@web-ts-toolkit/access-router 0.43.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -2
- package/advanced.d.mts +655 -19
- package/advanced.d.ts +655 -19
- package/advanced.js +478 -29
- package/advanced.mjs +478 -29
- package/index.d.mts +18 -10
- package/index.d.ts +18 -10
- package/index.js +1699 -262
- package/index.mjs +1576 -137
- package/llms.txt +19 -0
- package/package.json +18 -6
- package/{parsers-PMWujHUV.d.mts → parsers-DppKW-Fd.d.mts} +146 -26
- package/{parsers-PMWujHUV.d.ts → parsers-DppKW-Fd.d.ts} +146 -26
- package/processors.d.mts +151 -1
- package/processors.d.ts +151 -1
- package/processors.js +175 -8
- package/processors.mjs +170 -8
package/index.mjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { isPlainObject as
|
|
2
|
+
import { isPlainObject as isPlainObject20, isString as isString10, isUndefined as isUndefined4 } from "@web-ts-toolkit/utils";
|
|
3
3
|
|
|
4
4
|
// src/middleware.ts
|
|
5
5
|
import JsonRouter2 from "@web-ts-toolkit/express-json-router";
|
|
6
|
-
import { isArray as
|
|
6
|
+
import { isArray as isArray12, isFunction as isFunction6, isPlainObject as isPlainObject16, isString as isString7 } from "@web-ts-toolkit/utils";
|
|
7
7
|
|
|
8
8
|
// src/core.ts
|
|
9
9
|
import {
|
|
10
10
|
castArray as castArray4,
|
|
11
11
|
compact as compact3,
|
|
12
12
|
forEach as forEach7,
|
|
13
|
-
isArray as
|
|
13
|
+
isArray as isArray11,
|
|
14
14
|
isBoolean as isBoolean5,
|
|
15
15
|
isFunction as isFunction5,
|
|
16
16
|
isString as isString6,
|
|
@@ -784,6 +784,29 @@ var cloneConfigValue = (value) => {
|
|
|
784
784
|
])
|
|
785
785
|
);
|
|
786
786
|
};
|
|
787
|
+
var cloneForRead = (value) => {
|
|
788
|
+
if (Array.isArray(value)) {
|
|
789
|
+
if (Object.isFrozen(value)) {
|
|
790
|
+
return value;
|
|
791
|
+
}
|
|
792
|
+
return value.map((item) => cloneForRead(item));
|
|
793
|
+
}
|
|
794
|
+
if (!value || typeof value !== "object") {
|
|
795
|
+
return value;
|
|
796
|
+
}
|
|
797
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) {
|
|
798
|
+
return value;
|
|
799
|
+
}
|
|
800
|
+
if (Object.isFrozen(value)) {
|
|
801
|
+
return value;
|
|
802
|
+
}
|
|
803
|
+
return Object.fromEntries(
|
|
804
|
+
Object.entries(value).map(([entryKey, entryValue]) => [
|
|
805
|
+
entryKey,
|
|
806
|
+
cloneForRead(entryValue)
|
|
807
|
+
])
|
|
808
|
+
);
|
|
809
|
+
};
|
|
787
810
|
var freezeConfigValue = (value) => {
|
|
788
811
|
if (!value || typeof value !== "object") {
|
|
789
812
|
return value;
|
|
@@ -799,6 +822,7 @@ var freezeConfigValue = (value) => {
|
|
|
799
822
|
}
|
|
800
823
|
return value;
|
|
801
824
|
};
|
|
825
|
+
var cloneAndFreezeForStore = (value) => freezeConfigValue(cloneForRead(value));
|
|
802
826
|
var getNestedOption = (manager, key, defaultValue) => {
|
|
803
827
|
const keys2 = String(key).split(".");
|
|
804
828
|
if (keys2.length === 1) {
|
|
@@ -828,6 +852,14 @@ var OptionsManager = class {
|
|
|
828
852
|
target[keystr] = value;
|
|
829
853
|
_this.listeners[keystr] && _this.listeners[keystr].call(_this, value, keystr, target, oldvalue);
|
|
830
854
|
return true;
|
|
855
|
+
},
|
|
856
|
+
defineProperty(target, key, descriptor) {
|
|
857
|
+
const keystr = String(key);
|
|
858
|
+
const oldvalue = target[keystr];
|
|
859
|
+
const result = Reflect.defineProperty(target, key, descriptor);
|
|
860
|
+
const newValue = target[keystr];
|
|
861
|
+
_this.listeners[keystr] && _this.listeners[keystr].call(_this, newValue, keystr, target, oldvalue);
|
|
862
|
+
return result;
|
|
831
863
|
}
|
|
832
864
|
});
|
|
833
865
|
}
|
|
@@ -841,18 +873,18 @@ var OptionsManager = class {
|
|
|
841
873
|
set(key, value) {
|
|
842
874
|
const path = toPath(key);
|
|
843
875
|
if (path.length <= 1) {
|
|
844
|
-
set2(this.currentOptions, path, this.preserveKeys.has(String(path[0])) ? value :
|
|
876
|
+
set2(this.currentOptions, path, this.preserveKeys.has(String(path[0])) ? value : cloneAndFreezeForStore(value));
|
|
845
877
|
return;
|
|
846
878
|
}
|
|
847
879
|
const [rootKey, ...nestedPath] = path;
|
|
848
880
|
const currentRoot = get2(this.currentOptions, [rootKey], void 0);
|
|
849
881
|
const baseRoot = isPlainObject8(currentRoot) ? currentRoot : {};
|
|
850
882
|
const nextRoot = cloneConfigValue(baseRoot);
|
|
851
|
-
set2(nextRoot, nestedPath,
|
|
852
|
-
set2(this.currentOptions, [rootKey], nextRoot);
|
|
883
|
+
set2(nextRoot, nestedPath, cloneForRead(value));
|
|
884
|
+
set2(this.currentOptions, [rootKey], freezeConfigValue(nextRoot));
|
|
853
885
|
}
|
|
854
886
|
fetch() {
|
|
855
|
-
const cloned =
|
|
887
|
+
const cloned = cloneForRead(this.currentOptions);
|
|
856
888
|
for (const key of this.preserveKeys) {
|
|
857
889
|
if (key in this.currentOptions) {
|
|
858
890
|
cloned[key] = this.currentOptions[key];
|
|
@@ -861,20 +893,20 @@ var OptionsManager = class {
|
|
|
861
893
|
return freezeConfigValue(cloned);
|
|
862
894
|
}
|
|
863
895
|
assign(options) {
|
|
864
|
-
const cloned =
|
|
896
|
+
const cloned = cloneForRead(options);
|
|
865
897
|
for (const key of this.preserveKeys) {
|
|
866
898
|
if (key in options) {
|
|
867
899
|
cloned[key] = options[key];
|
|
868
900
|
}
|
|
869
901
|
}
|
|
870
|
-
assign(this.currentOptions, cloned);
|
|
902
|
+
assign(this.currentOptions, freezeConfigValue(cloned));
|
|
871
903
|
}
|
|
872
904
|
onchange(key, func) {
|
|
873
905
|
set2(this.listeners, key, func);
|
|
874
906
|
return this;
|
|
875
907
|
}
|
|
876
908
|
snapshot() {
|
|
877
|
-
const cloned =
|
|
909
|
+
const cloned = cloneForRead(this.currentOptions);
|
|
878
910
|
for (const key of this.preserveKeys) {
|
|
879
911
|
if (key in this.currentOptions) {
|
|
880
912
|
cloned[key] = this.currentOptions[key];
|
|
@@ -890,13 +922,13 @@ var OptionsManager = class {
|
|
|
890
922
|
delete current[key];
|
|
891
923
|
}
|
|
892
924
|
}
|
|
893
|
-
const cloned =
|
|
925
|
+
const cloned = cloneForRead(snap);
|
|
894
926
|
for (const key of this.preserveKeys) {
|
|
895
927
|
if (key in snap) {
|
|
896
928
|
cloned[key] = snap[key];
|
|
897
929
|
}
|
|
898
930
|
}
|
|
899
|
-
assign(current, cloned);
|
|
931
|
+
assign(current, freezeConfigValue(cloned));
|
|
900
932
|
}
|
|
901
933
|
};
|
|
902
934
|
|
|
@@ -911,7 +943,9 @@ var defaultRequestComplexity = {
|
|
|
911
943
|
maxIncludeCount: 10,
|
|
912
944
|
maxSubQueryCount: 10,
|
|
913
945
|
maxBulkConcurrency: 10,
|
|
914
|
-
maxHookConcurrency: 10
|
|
946
|
+
maxHookConcurrency: 10,
|
|
947
|
+
maxCorrelatedQueries: 100,
|
|
948
|
+
maxCorrelatedDepth: 5
|
|
915
949
|
};
|
|
916
950
|
var normalizePositiveInteger = (value, fallback) => {
|
|
917
951
|
return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : fallback;
|
|
@@ -931,6 +965,14 @@ var resolveRequestComplexity = (options) => ({
|
|
|
931
965
|
maxHookConcurrency: normalizePositiveInteger(
|
|
932
966
|
options?.maxHookConcurrency,
|
|
933
967
|
defaultRequestComplexity.maxHookConcurrency
|
|
968
|
+
),
|
|
969
|
+
maxCorrelatedQueries: normalizePositiveInteger(
|
|
970
|
+
options?.maxCorrelatedQueries,
|
|
971
|
+
defaultRequestComplexity.maxCorrelatedQueries
|
|
972
|
+
),
|
|
973
|
+
maxCorrelatedDepth: normalizePositiveInteger(
|
|
974
|
+
options?.maxCorrelatedDepth,
|
|
975
|
+
defaultRequestComplexity.maxCorrelatedDepth
|
|
934
976
|
)
|
|
935
977
|
});
|
|
936
978
|
function validateRequestComplexity(value, options, scope = "request") {
|
|
@@ -1366,8 +1408,7 @@ var AccessRuntime = class {
|
|
|
1366
1408
|
}
|
|
1367
1409
|
setDataOptions(dataName, options) {
|
|
1368
1410
|
const manager = this.getOrCreateDataOptions(dataName);
|
|
1369
|
-
|
|
1370
|
-
manager.assign({ ...currentDataOptions, ...options });
|
|
1411
|
+
manager.assign(options);
|
|
1371
1412
|
}
|
|
1372
1413
|
setDataOption(dataName, key, value) {
|
|
1373
1414
|
const manager = this.getOrCreateDataOptions(dataName);
|
|
@@ -1377,6 +1418,11 @@ var AccessRuntime = class {
|
|
|
1377
1418
|
const manager = this.getOrCreateDataOptions(dataName);
|
|
1378
1419
|
return manager.fetch();
|
|
1379
1420
|
}
|
|
1421
|
+
getDataSnapshot(dataName) {
|
|
1422
|
+
const manager = this.getOrCreateDataOptions(dataName);
|
|
1423
|
+
const snapshot = manager.get("data");
|
|
1424
|
+
return snapshot ?? [];
|
|
1425
|
+
}
|
|
1380
1426
|
getDataOption(dataName, key, defaultValue) {
|
|
1381
1427
|
const manager = this.getOrCreateDataOptions(dataName);
|
|
1382
1428
|
return getNestedOption(manager, key, defaultValue);
|
|
@@ -1498,6 +1544,9 @@ var getRuntime4 = () => getActiveRuntime() ?? defaultRuntime;
|
|
|
1498
1544
|
var getDataOptions = (dataName) => {
|
|
1499
1545
|
return getRuntime4().getDataOptions(dataName);
|
|
1500
1546
|
};
|
|
1547
|
+
var getDataSnapshot = (dataName) => {
|
|
1548
|
+
return getRuntime4().getDataSnapshot(dataName);
|
|
1549
|
+
};
|
|
1501
1550
|
var getDataOption = (dataName, key, defaultValue) => {
|
|
1502
1551
|
return getRuntime4().getDataOption(dataName, key, defaultValue);
|
|
1503
1552
|
};
|
|
@@ -1507,21 +1556,475 @@ var getRuntime5 = () => getActiveRuntime() ?? defaultRuntime;
|
|
|
1507
1556
|
var getModelRef = (modelName, refPath) => {
|
|
1508
1557
|
return getRuntime5().getModelRef(modelName, refPath);
|
|
1509
1558
|
};
|
|
1510
|
-
var getModelSub = (modelName) => {
|
|
1511
|
-
return getRuntime5().getModelSub(modelName);
|
|
1512
|
-
};
|
|
1513
1559
|
|
|
1514
1560
|
// src/services/base.ts
|
|
1515
1561
|
import {
|
|
1516
1562
|
castArray,
|
|
1563
|
+
cloneDeep as cloneDeep2,
|
|
1517
1564
|
compact,
|
|
1518
1565
|
flatten,
|
|
1519
1566
|
forEach as forEach3,
|
|
1520
1567
|
get as get4,
|
|
1521
|
-
isArray as
|
|
1522
|
-
isPlainObject as
|
|
1568
|
+
isArray as isArray7,
|
|
1569
|
+
isPlainObject as isPlainObject12,
|
|
1523
1570
|
uniq
|
|
1524
1571
|
} from "@web-ts-toolkit/utils";
|
|
1572
|
+
|
|
1573
|
+
// src/correlated-includes.ts
|
|
1574
|
+
import { cloneDeep, isArray as isArray6, isPlainObject as isPlainObject11 } from "@web-ts-toolkit/utils";
|
|
1575
|
+
var PARENT_MARKER_KEY = "$parent";
|
|
1576
|
+
var ESCAPE_WRAPPER_KEY = "$escape";
|
|
1577
|
+
var SUBQUERY_PAYLOAD_KEYS = /* @__PURE__ */ new Set(["$$sq", "$$date"]);
|
|
1578
|
+
var MARKER_FORBIDDEN_OPERATOR_KEYS = /* @__PURE__ */ new Set(["$text", "$where", "$comment"]);
|
|
1579
|
+
var LOGICAL_CLAUSE_KEYS = /* @__PURE__ */ new Set(["$and", "$or", "$nor"]);
|
|
1580
|
+
var DANGEROUS_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
1581
|
+
var FIELD_SEGMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_$]*$/;
|
|
1582
|
+
var ARRAY_INDEX_SEGMENT_PATTERN = /^(0|[1-9]\d*)$/;
|
|
1583
|
+
var ownKeys = (value) => Object.keys(value);
|
|
1584
|
+
function isParentRefMarker(value) {
|
|
1585
|
+
return isPlainObject11(value) && ownKeys(value).length === 1 && ownKeys(value)[0] === PARENT_MARKER_KEY;
|
|
1586
|
+
}
|
|
1587
|
+
function isEscapedParentRef(value) {
|
|
1588
|
+
return isPlainObject11(value) && ownKeys(value).length === 1 && ownKeys(value)[0] === ESCAPE_WRAPPER_KEY;
|
|
1589
|
+
}
|
|
1590
|
+
function hasMarkerKey(value) {
|
|
1591
|
+
return ownKeys(value).includes(PARENT_MARKER_KEY) || ownKeys(value).includes(ESCAPE_WRAPPER_KEY);
|
|
1592
|
+
}
|
|
1593
|
+
function isValidParentRefPath(path) {
|
|
1594
|
+
if (typeof path !== "string" || path.length === 0) return false;
|
|
1595
|
+
const segments = path.split(".");
|
|
1596
|
+
return segments.every(
|
|
1597
|
+
(segment) => (FIELD_SEGMENT_PATTERN.test(segment) || ARRAY_INDEX_SEGMENT_PATTERN.test(segment)) && !DANGEROUS_PATH_SEGMENTS.has(segment)
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
function isValidCorrelatedOutputPath(path) {
|
|
1601
|
+
return typeof path === "string" && path.length > 0 && isValidFieldPath(path) && path !== "_id" && !path.startsWith("$");
|
|
1602
|
+
}
|
|
1603
|
+
function containsParentMarkerShape(value) {
|
|
1604
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1605
|
+
const visit = (current) => {
|
|
1606
|
+
if (isParentRefMarker(current) || isEscapedParentRef(current)) return true;
|
|
1607
|
+
if (isArray6(current)) {
|
|
1608
|
+
if (seen.has(current)) return false;
|
|
1609
|
+
seen.add(current);
|
|
1610
|
+
return current.some(visit);
|
|
1611
|
+
}
|
|
1612
|
+
if (isPlainObject11(current)) {
|
|
1613
|
+
if (seen.has(current)) return false;
|
|
1614
|
+
seen.add(current);
|
|
1615
|
+
if (hasMarkerKey(current)) return true;
|
|
1616
|
+
return Object.values(current).some(visit);
|
|
1617
|
+
}
|
|
1618
|
+
return false;
|
|
1619
|
+
};
|
|
1620
|
+
return visit(value);
|
|
1621
|
+
}
|
|
1622
|
+
function scanTemplateValueNode(value, path, issues) {
|
|
1623
|
+
if (isEscapedParentRef(value)) {
|
|
1624
|
+
const inner = value.$escape;
|
|
1625
|
+
if (!isParentRefMarker(inner) || typeof inner.$parent !== "string" || (inner.$parent?.length ?? 0) === 0) {
|
|
1626
|
+
issues.push({
|
|
1627
|
+
message: 'Invalid $escape payload: expected { "$escape": { "$parent": "<field>" } }',
|
|
1628
|
+
path
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
if (isParentRefMarker(value)) {
|
|
1634
|
+
if (!isValidParentRefPath(value.$parent)) {
|
|
1635
|
+
issues.push({
|
|
1636
|
+
message: `Invalid $parent reference path at ${path.length === 0 ? "#" : path.join(".")}`,
|
|
1637
|
+
path
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
if (isPlainObject11(value)) {
|
|
1643
|
+
if (hasMarkerKey(value)) {
|
|
1644
|
+
issues.push({
|
|
1645
|
+
message: 'Malformed $parent reference: objects carrying $parent must be exactly { "$parent": "<field>" }',
|
|
1646
|
+
path
|
|
1647
|
+
});
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
scanTemplateObjectEntries(value, path, issues);
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
if (isArray6(value)) {
|
|
1654
|
+
value.forEach((item, index) => scanTemplateValueNode(item, [...path, index], issues));
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
function scanTemplateObjectEntries(obj, basePath, issues) {
|
|
1658
|
+
for (const [key, child] of Object.entries(obj)) {
|
|
1659
|
+
const childPath = [...basePath, key];
|
|
1660
|
+
if (key === PARENT_MARKER_KEY) {
|
|
1661
|
+
issues.push({ message: "$parent is never a field name in correlated templates", path: childPath });
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
if (SUBQUERY_PAYLOAD_KEYS.has(key)) {
|
|
1665
|
+
if (containsParentMarkerShape(child)) {
|
|
1666
|
+
issues.push({
|
|
1667
|
+
message: `Unsupported $parent reference inside ${key}: markers are not resolved in subquery payloads`,
|
|
1668
|
+
path: childPath
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
if (MARKER_FORBIDDEN_OPERATOR_KEYS.has(key)) {
|
|
1674
|
+
if (containsParentMarkerShape(child)) {
|
|
1675
|
+
issues.push({
|
|
1676
|
+
message: `Unsupported $parent reference inside ${key}: markers are not resolved in ${key} values`,
|
|
1677
|
+
path: childPath
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
continue;
|
|
1681
|
+
}
|
|
1682
|
+
if (key === "$elemMatch") {
|
|
1683
|
+
if (isParentRefMarker(child) || isEscapedParentRef(child)) {
|
|
1684
|
+
issues.push({
|
|
1685
|
+
message: "Unsupported $parent reference as a direct $elemMatch value",
|
|
1686
|
+
path: childPath
|
|
1687
|
+
});
|
|
1688
|
+
continue;
|
|
1689
|
+
}
|
|
1690
|
+
scanTemplateValueNode(child, childPath, issues);
|
|
1691
|
+
continue;
|
|
1692
|
+
}
|
|
1693
|
+
if (LOGICAL_CLAUSE_KEYS.has(key)) {
|
|
1694
|
+
if (!isArray6(child)) {
|
|
1695
|
+
scanTemplateValueNode(child, childPath, issues);
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
child.forEach((clause, index) => {
|
|
1699
|
+
const clausePath = [...childPath, index];
|
|
1700
|
+
if (isParentRefMarker(clause) || isEscapedParentRef(clause)) {
|
|
1701
|
+
issues.push({
|
|
1702
|
+
message: `Unsupported $parent reference as a direct ${key} clause: clauses must be filter objects`,
|
|
1703
|
+
path: clausePath
|
|
1704
|
+
});
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
if (isPlainObject11(clause)) {
|
|
1708
|
+
scanTemplateObjectEntries(clause, clausePath, issues);
|
|
1709
|
+
}
|
|
1710
|
+
});
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
scanTemplateValueNode(child, childPath, issues);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
function validateCorrelatedFilterTemplate(filter2) {
|
|
1717
|
+
const issues = [];
|
|
1718
|
+
if (!isPlainObject11(filter2)) {
|
|
1719
|
+
issues.push({ message: "Correlated include filter must be an object", path: [] });
|
|
1720
|
+
return issues;
|
|
1721
|
+
}
|
|
1722
|
+
if (isParentRefMarker(filter2) || isEscapedParentRef(filter2)) {
|
|
1723
|
+
issues.push({ message: "Correlated include filter must not itself be a $parent reference", path: [] });
|
|
1724
|
+
return issues;
|
|
1725
|
+
}
|
|
1726
|
+
scanTemplateObjectEntries(filter2, [], issues);
|
|
1727
|
+
return issues;
|
|
1728
|
+
}
|
|
1729
|
+
function validateCorrelatedIdTemplate(id) {
|
|
1730
|
+
if (typeof id === "string") {
|
|
1731
|
+
return id.length > 0 ? [] : [{ message: "Correlated include id must be a non-empty string or $parent reference", path: [] }];
|
|
1732
|
+
}
|
|
1733
|
+
if (isEscapedParentRef(id)) {
|
|
1734
|
+
return [{ message: "Unsupported $escape as a correlated include id", path: [] }];
|
|
1735
|
+
}
|
|
1736
|
+
if (isParentRefMarker(id)) {
|
|
1737
|
+
return isValidParentRefPath(id.$parent) ? [] : [{ message: "Invalid $parent reference path in correlated include id", path: [] }];
|
|
1738
|
+
}
|
|
1739
|
+
return [{ message: "Correlated include id must be a string or $parent reference", path: [] }];
|
|
1740
|
+
}
|
|
1741
|
+
function findArgsMarkerIssue(value, where) {
|
|
1742
|
+
if (value !== void 0 && containsParentMarkerShape(value)) {
|
|
1743
|
+
return {
|
|
1744
|
+
message: `Unsupported $parent reference in ${where}: references are only supported in filter and id positions`,
|
|
1745
|
+
path: []
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
return null;
|
|
1749
|
+
}
|
|
1750
|
+
function collectFilterReferencePaths(filter2) {
|
|
1751
|
+
const paths = [];
|
|
1752
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1753
|
+
const visitValue = (value) => {
|
|
1754
|
+
if (isParentRefMarker(value)) {
|
|
1755
|
+
if (typeof value.$parent === "string") {
|
|
1756
|
+
paths.push(value.$parent);
|
|
1757
|
+
}
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
if (isEscapedParentRef(value)) return;
|
|
1761
|
+
if (isArray6(value)) {
|
|
1762
|
+
if (seen.has(value)) return;
|
|
1763
|
+
seen.add(value);
|
|
1764
|
+
value.forEach(visitValue);
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
if (isPlainObject11(value)) {
|
|
1768
|
+
if (seen.has(value)) return;
|
|
1769
|
+
seen.add(value);
|
|
1770
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1771
|
+
if (SUBQUERY_PAYLOAD_KEYS.has(key) || MARKER_FORBIDDEN_OPERATOR_KEYS.has(key)) continue;
|
|
1772
|
+
visitValue(child);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
visitValue(filter2);
|
|
1777
|
+
return paths;
|
|
1778
|
+
}
|
|
1779
|
+
function collectCorrelatedReferencePaths(include) {
|
|
1780
|
+
const paths = [];
|
|
1781
|
+
if (isParentRefMarker(include.id) && typeof include.id !== "string") {
|
|
1782
|
+
const ref = include.id;
|
|
1783
|
+
if (typeof ref.$parent === "string") paths.push(ref.$parent);
|
|
1784
|
+
} else if (typeof include.id === "string") {
|
|
1785
|
+
}
|
|
1786
|
+
const filter2 = include.filter;
|
|
1787
|
+
if (filter2 !== void 0) {
|
|
1788
|
+
paths.push(...collectFilterReferencePaths(filter2));
|
|
1789
|
+
}
|
|
1790
|
+
return [...new Set(paths)];
|
|
1791
|
+
}
|
|
1792
|
+
var CorrelatedReferenceError = class extends Error {
|
|
1793
|
+
constructor(message) {
|
|
1794
|
+
super(message);
|
|
1795
|
+
this.name = "CorrelatedReferenceError";
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
var UNRESOLVABLE = /* @__PURE__ */ Symbol("correlated-include-unresolvable");
|
|
1799
|
+
function getOwnPathValue(parent, path) {
|
|
1800
|
+
const segments = path.split(".");
|
|
1801
|
+
let current = parent;
|
|
1802
|
+
for (const segment of segments) {
|
|
1803
|
+
if (DANGEROUS_PATH_SEGMENTS.has(segment)) {
|
|
1804
|
+
throw new CorrelatedReferenceError(`Invalid $parent reference path: ${path}`);
|
|
1805
|
+
}
|
|
1806
|
+
if (typeof current !== "object" || current === null) return void 0;
|
|
1807
|
+
if (!Object.prototype.hasOwnProperty.call(current, segment)) return void 0;
|
|
1808
|
+
current = current[segment];
|
|
1809
|
+
}
|
|
1810
|
+
return current;
|
|
1811
|
+
}
|
|
1812
|
+
function cloneSubstitutedValue(value) {
|
|
1813
|
+
if (isPlainObject11(value) || isArray6(value)) return cloneDeep(value);
|
|
1814
|
+
return value;
|
|
1815
|
+
}
|
|
1816
|
+
function literalParentRefObject(path) {
|
|
1817
|
+
const literal = {};
|
|
1818
|
+
Object.defineProperty(literal, PARENT_MARKER_KEY, {
|
|
1819
|
+
value: path,
|
|
1820
|
+
writable: true,
|
|
1821
|
+
enumerable: true,
|
|
1822
|
+
configurable: true
|
|
1823
|
+
});
|
|
1824
|
+
return literal;
|
|
1825
|
+
}
|
|
1826
|
+
function resolveObjectEntries(obj, parent) {
|
|
1827
|
+
const out = {};
|
|
1828
|
+
for (const [key, child] of Object.entries(obj)) {
|
|
1829
|
+
if (key === PARENT_MARKER_KEY) {
|
|
1830
|
+
throw new CorrelatedReferenceError("$parent is never a field name in correlated templates");
|
|
1831
|
+
}
|
|
1832
|
+
if (SUBQUERY_PAYLOAD_KEYS.has(key)) {
|
|
1833
|
+
if (containsParentMarkerShape(child)) {
|
|
1834
|
+
throw new CorrelatedReferenceError(`Unsupported $parent reference inside ${key}`);
|
|
1835
|
+
}
|
|
1836
|
+
out[key] = cloneSubstitutedValue(child);
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
if (MARKER_FORBIDDEN_OPERATOR_KEYS.has(key)) {
|
|
1840
|
+
if (containsParentMarkerShape(child)) {
|
|
1841
|
+
throw new CorrelatedReferenceError(`Unsupported $parent reference inside ${key}`);
|
|
1842
|
+
}
|
|
1843
|
+
out[key] = cloneSubstitutedValue(child);
|
|
1844
|
+
continue;
|
|
1845
|
+
}
|
|
1846
|
+
if (key === "$elemMatch") {
|
|
1847
|
+
if (isParentRefMarker(child) || isEscapedParentRef(child)) {
|
|
1848
|
+
throw new CorrelatedReferenceError("Unsupported $parent reference as a direct $elemMatch value");
|
|
1849
|
+
}
|
|
1850
|
+
out[key] = resolveValueNode(child, parent, false);
|
|
1851
|
+
continue;
|
|
1852
|
+
}
|
|
1853
|
+
if (LOGICAL_CLAUSE_KEYS.has(key)) {
|
|
1854
|
+
if (!isArray6(child)) {
|
|
1855
|
+
out[key] = resolveValueNode(child, parent, false);
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1858
|
+
out[key] = child.map((clause) => {
|
|
1859
|
+
if (isParentRefMarker(clause) || isEscapedParentRef(clause)) {
|
|
1860
|
+
throw new CorrelatedReferenceError(`Unsupported $parent reference as a direct ${key} clause`);
|
|
1861
|
+
}
|
|
1862
|
+
if (!isPlainObject11(clause)) return cloneSubstitutedValue(clause);
|
|
1863
|
+
return resolveObjectEntries(clause, parent);
|
|
1864
|
+
});
|
|
1865
|
+
continue;
|
|
1866
|
+
}
|
|
1867
|
+
if (key.startsWith("$")) {
|
|
1868
|
+
out[key] = resolveValueNode(child, parent, false);
|
|
1869
|
+
continue;
|
|
1870
|
+
}
|
|
1871
|
+
out[key] = resolveValueNode(child, parent, true);
|
|
1872
|
+
}
|
|
1873
|
+
return out;
|
|
1874
|
+
}
|
|
1875
|
+
function resolveValueNode(template, parent, bare) {
|
|
1876
|
+
if (isEscapedParentRef(template)) {
|
|
1877
|
+
const inner = template.$escape;
|
|
1878
|
+
if (!isParentRefMarker(inner) || typeof inner.$parent !== "string" || (inner.$parent?.length ?? 0) === 0) {
|
|
1879
|
+
throw new CorrelatedReferenceError('Invalid $escape payload: expected { "$escape": { "$parent": "<field>" } }');
|
|
1880
|
+
}
|
|
1881
|
+
return literalParentRefObject(inner.$parent);
|
|
1882
|
+
}
|
|
1883
|
+
if (isParentRefMarker(template)) {
|
|
1884
|
+
const path = template.$parent;
|
|
1885
|
+
if (!isValidParentRefPath(path)) {
|
|
1886
|
+
throw new CorrelatedReferenceError(`Invalid $parent reference path: ${String(path)}`);
|
|
1887
|
+
}
|
|
1888
|
+
const value = getOwnPathValue(parent, path);
|
|
1889
|
+
if (value === void 0 || value === null) throw UNRESOLVABLE;
|
|
1890
|
+
if (bare && isPlainObject11(value)) return { $eq: cloneDeep(value) };
|
|
1891
|
+
return cloneSubstitutedValue(value);
|
|
1892
|
+
}
|
|
1893
|
+
if (isPlainObject11(template)) {
|
|
1894
|
+
if (hasMarkerKey(template)) {
|
|
1895
|
+
throw new CorrelatedReferenceError(
|
|
1896
|
+
'Malformed $parent reference: objects carrying $parent must be exactly { "$parent": "<field>" }'
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
return resolveObjectEntries(template, parent);
|
|
1900
|
+
}
|
|
1901
|
+
if (isArray6(template)) {
|
|
1902
|
+
return template.map((item) => resolveValueNode(item, parent, false));
|
|
1903
|
+
}
|
|
1904
|
+
return template;
|
|
1905
|
+
}
|
|
1906
|
+
function isUnresolvableSignal(error2) {
|
|
1907
|
+
return error2 === UNRESOLVABLE;
|
|
1908
|
+
}
|
|
1909
|
+
function resolveCorrelatedIdTemplate(id, parent) {
|
|
1910
|
+
try {
|
|
1911
|
+
if (typeof id === "string") {
|
|
1912
|
+
if (id.length === 0) throw new CorrelatedReferenceError("Correlated include id must be a non-empty string");
|
|
1913
|
+
return { status: "resolved", id };
|
|
1914
|
+
}
|
|
1915
|
+
if (isParentRefMarker(id)) {
|
|
1916
|
+
const path = id.$parent;
|
|
1917
|
+
if (!isValidParentRefPath(path)) {
|
|
1918
|
+
throw new CorrelatedReferenceError(`Invalid $parent reference path: ${String(path)}`);
|
|
1919
|
+
}
|
|
1920
|
+
const value = getOwnPathValue(parent, path);
|
|
1921
|
+
if (typeof value === "string") return { status: "resolved", id: value };
|
|
1922
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
1923
|
+
return { status: "resolved", id: String(value) };
|
|
1924
|
+
}
|
|
1925
|
+
throw UNRESOLVABLE;
|
|
1926
|
+
}
|
|
1927
|
+
throw new CorrelatedReferenceError("Correlated include id must be a string or $parent reference");
|
|
1928
|
+
} catch (error2) {
|
|
1929
|
+
if (isUnresolvableSignal(error2)) return { status: "unresolvable" };
|
|
1930
|
+
throw error2;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
function resolveCorrelatedFilterTemplate(filter2, parent) {
|
|
1934
|
+
try {
|
|
1935
|
+
if (!isPlainObject11(filter2) || isParentRefMarker(filter2) || isEscapedParentRef(filter2)) {
|
|
1936
|
+
throw new CorrelatedReferenceError("Correlated include filter must be an object");
|
|
1937
|
+
}
|
|
1938
|
+
return { status: "resolved", filter: resolveObjectEntries(filter2, parent) };
|
|
1939
|
+
} catch (error2) {
|
|
1940
|
+
if (isUnresolvableSignal(error2)) return { status: "unresolvable" };
|
|
1941
|
+
throw error2;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
function validateExpandedCorrelatedOperands(expanded, limits) {
|
|
1945
|
+
return validateRequestComplexity(expanded, limits ?? null, "filter");
|
|
1946
|
+
}
|
|
1947
|
+
var MAX_NESTED_INCLUDE_DEPTH = 10;
|
|
1948
|
+
function validateCorrelatedArgsShape(args, op, depth) {
|
|
1949
|
+
if (args === void 0) return [];
|
|
1950
|
+
if (!isPlainObject11(args)) return ["Correlated include args must be an object"];
|
|
1951
|
+
const record = args;
|
|
1952
|
+
const allowed = op === "list" ? /* @__PURE__ */ new Set(["select", "sort", "skip", "limit", "page", "pageSize", "include"]) : /* @__PURE__ */ new Set(["select", "sort", "include"]);
|
|
1953
|
+
for (const key of Object.keys(record)) {
|
|
1954
|
+
if (!allowed.has(key)) return [`Unsupported correlated include arg: ${key}`];
|
|
1955
|
+
}
|
|
1956
|
+
if (op === "count" && Object.keys(record).length > 0) return ["Count includes accept no args"];
|
|
1957
|
+
const selectIssue = findArgsMarkerIssue(record.select, "args.select");
|
|
1958
|
+
if (selectIssue) return [selectIssue.message];
|
|
1959
|
+
const sortIssue = findArgsMarkerIssue(record.sort, "args.sort");
|
|
1960
|
+
if (sortIssue) return [sortIssue.message];
|
|
1961
|
+
if (record.include !== void 0) {
|
|
1962
|
+
if (depth >= MAX_NESTED_INCLUDE_DEPTH) return ["Correlated include nesting exceeds the supported depth"];
|
|
1963
|
+
const nested = Array.isArray(record.include) ? record.include : [record.include];
|
|
1964
|
+
for (const entry of nested) {
|
|
1965
|
+
const errors = validateCorrelatedIncludeShape(entry, depth + 1);
|
|
1966
|
+
if (errors.length > 0) return errors;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
return [];
|
|
1970
|
+
}
|
|
1971
|
+
function validateCorrelatedIncludeShape(entry, depth = 0) {
|
|
1972
|
+
if (!isPlainObject11(entry)) return ["Correlated include must be an object"];
|
|
1973
|
+
const record = entry;
|
|
1974
|
+
if (record.mode !== "correlated") return ['Correlated include mode must be "correlated"'];
|
|
1975
|
+
if (typeof record.model !== "string" || record.model.length === 0)
|
|
1976
|
+
return ["Correlated include model must be a non-empty string"];
|
|
1977
|
+
if (record.op !== "list" && record.op !== "read" && record.op !== "count") {
|
|
1978
|
+
return ['Correlated include op must be one of "list", "read", or "count"'];
|
|
1979
|
+
}
|
|
1980
|
+
if (!isValidCorrelatedOutputPath(record.path))
|
|
1981
|
+
return [`Invalid correlated include output path: ${String(record.path)}`];
|
|
1982
|
+
if ("localField" in record || "foreignField" in record) {
|
|
1983
|
+
return ["Correlated includes must not carry localField or foreignField"];
|
|
1984
|
+
}
|
|
1985
|
+
if (record.options !== void 0 && !(isPlainObject11(record.options) && Object.keys(record.options).length === 0)) {
|
|
1986
|
+
return ["Correlated include options must be absent or empty"];
|
|
1987
|
+
}
|
|
1988
|
+
const hasId = record.id !== void 0;
|
|
1989
|
+
const hasFilter = record.filter !== void 0;
|
|
1990
|
+
if (record.op === "read") {
|
|
1991
|
+
if (hasId === hasFilter) return ['Correlated read includes require exactly one of "id" or "filter"'];
|
|
1992
|
+
if (hasId) {
|
|
1993
|
+
const issues = validateCorrelatedIdTemplate(record.id);
|
|
1994
|
+
if (issues.length > 0) return [issues[0].message];
|
|
1995
|
+
} else {
|
|
1996
|
+
const issues = validateCorrelatedFilterTemplate(record.filter);
|
|
1997
|
+
if (issues.length > 0) return [issues[0].message];
|
|
1998
|
+
}
|
|
1999
|
+
} else {
|
|
2000
|
+
if (!hasFilter || hasId) return [`Correlated ${record.op} includes require a filter and no id`];
|
|
2001
|
+
const issues = validateCorrelatedFilterTemplate(record.filter);
|
|
2002
|
+
if (issues.length > 0) return [issues[0].message];
|
|
2003
|
+
}
|
|
2004
|
+
return validateCorrelatedArgsShape(record.args, record.op, depth);
|
|
2005
|
+
}
|
|
2006
|
+
function isCorrelatedInclude(value) {
|
|
2007
|
+
return isPlainObject11(value) && value.mode === "correlated";
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
// src/services/base.ts
|
|
2011
|
+
var correlatedExecStates = /* @__PURE__ */ new WeakMap();
|
|
2012
|
+
function getCorrelatedTemplateDepth(includes) {
|
|
2013
|
+
let max = 0;
|
|
2014
|
+
const visit = (entries, depth) => {
|
|
2015
|
+
const list = castArray(entries).filter(Boolean);
|
|
2016
|
+
for (const entry of list) {
|
|
2017
|
+
if (!isPlainObject12(entry)) continue;
|
|
2018
|
+
max = Math.max(max, depth);
|
|
2019
|
+
const nested = entry.args?.include;
|
|
2020
|
+
if (nested !== void 0) {
|
|
2021
|
+
visit(nested, depth + 1);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
if (includes.length > 0) visit(includes, 1);
|
|
2026
|
+
return max;
|
|
2027
|
+
}
|
|
1525
2028
|
var ClientRequestError = class extends Error {
|
|
1526
2029
|
constructor(result) {
|
|
1527
2030
|
super(String(result.code));
|
|
@@ -1536,11 +2039,11 @@ function validateClientFilter(filter2) {
|
|
|
1536
2039
|
}
|
|
1537
2040
|
const blockedOperators = /* @__PURE__ */ new Set(["$where", "$expr", "$function", "$accumulator"]);
|
|
1538
2041
|
const visit = (value, path) => {
|
|
1539
|
-
if (
|
|
2042
|
+
if (isArray7(value)) {
|
|
1540
2043
|
value.forEach((item, index) => visit(item, `${path}[${index}]`));
|
|
1541
2044
|
return;
|
|
1542
2045
|
}
|
|
1543
|
-
if (!
|
|
2046
|
+
if (!isPlainObject12(value)) return;
|
|
1544
2047
|
Object.entries(value).forEach(([key, child]) => {
|
|
1545
2048
|
const nextPath = path ? `${path}.${key}` : key;
|
|
1546
2049
|
if (blockedOperators.has(key)) {
|
|
@@ -1660,21 +2163,52 @@ var Base = class {
|
|
|
1660
2163
|
return this.req.macl.getPublicService(modelName);
|
|
1661
2164
|
}
|
|
1662
2165
|
processInclude(include) {
|
|
1663
|
-
const
|
|
1664
|
-
|
|
1665
|
-
|
|
2166
|
+
const items = compact(castArray(include));
|
|
2167
|
+
const legacy = [];
|
|
2168
|
+
const correlated = [];
|
|
2169
|
+
const correlatedReferenceFields = [];
|
|
2170
|
+
for (const entry of items) {
|
|
2171
|
+
if (isCorrelatedInclude(entry)) {
|
|
2172
|
+
const shapeErrors = validateCorrelatedIncludeShape(entry);
|
|
2173
|
+
if (shapeErrors.length > 0) {
|
|
2174
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, shapeErrors[0]);
|
|
2175
|
+
}
|
|
2176
|
+
correlated.push(entry);
|
|
2177
|
+
correlatedReferenceFields.push(...collectCorrelatedReferencePaths(entry));
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
if (containsParentMarkerShape(entry)) {
|
|
2181
|
+
this.throwClientRequestError(
|
|
2182
|
+
"bad_request" /* BadRequest */,
|
|
2183
|
+
'Legacy includes must not contain $parent markers; use a correlated include (mode: "correlated")'
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
const candidate = entry;
|
|
2187
|
+
if (candidate.model && candidate.op && candidate.path && candidate.localField && candidate.foreignField) {
|
|
2188
|
+
legacy.push(entry);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
const seenCorrelatedPaths = /* @__PURE__ */ new Set();
|
|
2192
|
+
for (const entry of correlated) {
|
|
2193
|
+
if (seenCorrelatedPaths.has(entry.path)) {
|
|
2194
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, `Duplicate correlated include output path: ${entry.path}`);
|
|
2195
|
+
}
|
|
2196
|
+
seenCorrelatedPaths.add(entry.path);
|
|
2197
|
+
}
|
|
1666
2198
|
let includeLocalFields = [];
|
|
1667
2199
|
let includePaths = [];
|
|
1668
|
-
forEach3(
|
|
2200
|
+
forEach3(legacy, (inc) => {
|
|
1669
2201
|
includeLocalFields.push(inc.localField);
|
|
1670
2202
|
includePaths.push(inc.path);
|
|
1671
2203
|
});
|
|
1672
2204
|
includeLocalFields = uniq(compact(includeLocalFields));
|
|
1673
2205
|
includePaths = uniq(compact(includePaths));
|
|
1674
2206
|
return {
|
|
1675
|
-
includes,
|
|
2207
|
+
includes: legacy,
|
|
2208
|
+
correlatedIncludes: correlated,
|
|
1676
2209
|
includeLocalFields,
|
|
1677
|
-
includePaths
|
|
2210
|
+
includePaths,
|
|
2211
|
+
correlatedReferenceFields: uniq(compact(correlatedReferenceFields))
|
|
1678
2212
|
};
|
|
1679
2213
|
}
|
|
1680
2214
|
getForeignKeyValues(value) {
|
|
@@ -1717,7 +2251,7 @@ var Base = class {
|
|
|
1717
2251
|
return ids.size;
|
|
1718
2252
|
}
|
|
1719
2253
|
sanitizeIncludeArgs(args) {
|
|
1720
|
-
if (!
|
|
2254
|
+
if (!isPlainObject12(args)) return {};
|
|
1721
2255
|
const { overrides: _clientOverrides, ...trustedArgs } = args;
|
|
1722
2256
|
return trustedArgs;
|
|
1723
2257
|
}
|
|
@@ -1730,10 +2264,16 @@ var Base = class {
|
|
|
1730
2264
|
if (!include) return docs;
|
|
1731
2265
|
const includes = compact(castArray(include));
|
|
1732
2266
|
if (includes.length === 0) return docs;
|
|
1733
|
-
const isSingle = !
|
|
2267
|
+
const isSingle = !isArray7(docs);
|
|
1734
2268
|
let docList = isSingle ? [docs] : docs;
|
|
1735
2269
|
for (let x = 0; x < includes.length; x++) {
|
|
1736
2270
|
const include2 = includes[x];
|
|
2271
|
+
if (isCorrelatedInclude(include2)) {
|
|
2272
|
+
this.throwClientRequestError(
|
|
2273
|
+
"bad_request" /* BadRequest */,
|
|
2274
|
+
"Correlated includes are not supported by the legacy include executor"
|
|
2275
|
+
);
|
|
2276
|
+
}
|
|
1737
2277
|
this.assertIncludeForeignField(include2);
|
|
1738
2278
|
switch (include2.op) {
|
|
1739
2279
|
case "count":
|
|
@@ -1812,6 +2352,356 @@ var Base = class {
|
|
|
1812
2352
|
}
|
|
1813
2353
|
return docs;
|
|
1814
2354
|
}
|
|
2355
|
+
getCorrelatedExecState() {
|
|
2356
|
+
const key = this.req;
|
|
2357
|
+
let state = correlatedExecStates.get(key);
|
|
2358
|
+
if (!state) {
|
|
2359
|
+
state = {
|
|
2360
|
+
totalQueries: 0,
|
|
2361
|
+
scheduler: new RequestConcurrencyScheduler(this.getRequestComplexity().maxBulkConcurrency)
|
|
2362
|
+
};
|
|
2363
|
+
correlatedExecStates.set(key, state);
|
|
2364
|
+
}
|
|
2365
|
+
return state;
|
|
2366
|
+
}
|
|
2367
|
+
claimCorrelatedQuerySlot() {
|
|
2368
|
+
const limits = this.getRequestComplexity();
|
|
2369
|
+
const state = this.getCorrelatedExecState();
|
|
2370
|
+
if (state.totalQueries >= limits.maxCorrelatedQueries) {
|
|
2371
|
+
this.throwClientRequestError(
|
|
2372
|
+
"bad_request" /* BadRequest */,
|
|
2373
|
+
`Correlated include query budget exceeded (max ${limits.maxCorrelatedQueries})`
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
state.totalQueries += 1;
|
|
2377
|
+
}
|
|
2378
|
+
sanitizeCorrelatedArgs(include) {
|
|
2379
|
+
const args = include.args ?? {};
|
|
2380
|
+
if (include.op === "list") {
|
|
2381
|
+
const { select: select2, sort: sort2, skip, limit, page, pageSize, include: nestedInclude2 } = args;
|
|
2382
|
+
return { select: select2, sort: sort2, skip, limit, page, pageSize, nestedInclude: nestedInclude2 };
|
|
2383
|
+
}
|
|
2384
|
+
const { select, sort, include: nestedInclude } = args;
|
|
2385
|
+
return { select, sort, nestedInclude };
|
|
2386
|
+
}
|
|
2387
|
+
async includeCorrelatedDocs(docs, correlatedIncludes, snapshots, depth = 0) {
|
|
2388
|
+
if (correlatedIncludes.length === 0) return docs;
|
|
2389
|
+
const limits = this.getRequestComplexity();
|
|
2390
|
+
const templateDepth = getCorrelatedTemplateDepth(correlatedIncludes);
|
|
2391
|
+
if (depth + templateDepth > limits.maxCorrelatedDepth) {
|
|
2392
|
+
this.throwClientRequestError(
|
|
2393
|
+
"bad_request" /* BadRequest */,
|
|
2394
|
+
`Correlated include depth exceeded (max ${limits.maxCorrelatedDepth})`
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
const state = this.getCorrelatedExecState();
|
|
2398
|
+
const resolvedSnapshots = snapshots ?? docs.map((doc) => cloneDeep2(toObject(doc)));
|
|
2399
|
+
for (const include of correlatedIncludes) {
|
|
2400
|
+
switch (include.op) {
|
|
2401
|
+
case "read":
|
|
2402
|
+
await this.includeCorrelatedRead(docs, include, resolvedSnapshots, state, depth);
|
|
2403
|
+
break;
|
|
2404
|
+
case "list":
|
|
2405
|
+
await this.includeCorrelatedList(docs, include, resolvedSnapshots, state, depth);
|
|
2406
|
+
break;
|
|
2407
|
+
case "count":
|
|
2408
|
+
await this.includeCorrelatedCount(docs, include, resolvedSnapshots, state, depth);
|
|
2409
|
+
break;
|
|
2410
|
+
default:
|
|
2411
|
+
this.throwClientRequestError(
|
|
2412
|
+
"bad_request" /* BadRequest */,
|
|
2413
|
+
`Unsupported correlated include op: ${include.op}`
|
|
2414
|
+
);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
return docs;
|
|
2418
|
+
}
|
|
2419
|
+
async includeCorrelatedRead(docs, include, snapshots, state, depth) {
|
|
2420
|
+
const svc = await this.getAuthorizedTargetService(include.model, "read");
|
|
2421
|
+
const { select, sort, nestedInclude } = this.sanitizeCorrelatedArgs(include);
|
|
2422
|
+
const hasId = include.id !== void 0;
|
|
2423
|
+
if (hasId) {
|
|
2424
|
+
const idTemplate = include.id;
|
|
2425
|
+
const tasks2 = docs.map((_, index) => ({ index, snapshot: snapshots[index] }));
|
|
2426
|
+
await state.scheduler.map(tasks2, async ({ index, snapshot }) => {
|
|
2427
|
+
let resolution;
|
|
2428
|
+
try {
|
|
2429
|
+
resolution = resolveCorrelatedIdTemplate(idTemplate, snapshot);
|
|
2430
|
+
} catch (error2) {
|
|
2431
|
+
if (error2 instanceof CorrelatedReferenceError) {
|
|
2432
|
+
throw new ClientRequestError({
|
|
2433
|
+
success: false,
|
|
2434
|
+
kind: "error",
|
|
2435
|
+
code: "bad_request" /* BadRequest */,
|
|
2436
|
+
errors: [{ detail: error2.message }]
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
throw error2;
|
|
2440
|
+
}
|
|
2441
|
+
if (resolution.status === "unresolvable") {
|
|
2442
|
+
setDocValue(docs[index], include.path, null);
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
const resolvedId = resolution.id;
|
|
2446
|
+
let idFilter;
|
|
2447
|
+
try {
|
|
2448
|
+
idFilter = await svc.genIDFilter(resolvedId);
|
|
2449
|
+
} catch (error2) {
|
|
2450
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2451
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2452
|
+
throw error2;
|
|
2453
|
+
}
|
|
2454
|
+
const authorized = await svc.genFilter("read", idFilter);
|
|
2455
|
+
if (authorized === false) {
|
|
2456
|
+
throw new ClientRequestError({ success: false, kind: "error", code: "forbidden" /* Forbidden */ });
|
|
2457
|
+
}
|
|
2458
|
+
this.claimCorrelatedQuerySlot();
|
|
2459
|
+
let result;
|
|
2460
|
+
try {
|
|
2461
|
+
result = await svc.findOne(
|
|
2462
|
+
{},
|
|
2463
|
+
{
|
|
2464
|
+
select,
|
|
2465
|
+
sort,
|
|
2466
|
+
include: nestedInclude,
|
|
2467
|
+
overrides: { filter: authorized }
|
|
2468
|
+
},
|
|
2469
|
+
{ access: "read", lean: true, includePermissions: false }
|
|
2470
|
+
);
|
|
2471
|
+
} catch (error2) {
|
|
2472
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2473
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2474
|
+
throw error2;
|
|
2475
|
+
}
|
|
2476
|
+
if (result.success) {
|
|
2477
|
+
setDocValue(docs[index], include.path, result.data);
|
|
2478
|
+
} else if (result.code === "not_found" /* NotFound */) {
|
|
2479
|
+
setDocValue(docs[index], include.path, null);
|
|
2480
|
+
} else {
|
|
2481
|
+
throw new ClientRequestError(result);
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
const filterTemplate = include.filter;
|
|
2487
|
+
const templateErrors = this.validateClientFilter(filterTemplate);
|
|
2488
|
+
if (templateErrors.length > 0) {
|
|
2489
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, templateErrors[0]);
|
|
2490
|
+
}
|
|
2491
|
+
let parsedTemplate;
|
|
2492
|
+
try {
|
|
2493
|
+
parsedTemplate = await this.parseClientData(
|
|
2494
|
+
filterTemplate,
|
|
2495
|
+
state.scheduler
|
|
2496
|
+
);
|
|
2497
|
+
} catch (error2) {
|
|
2498
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2499
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2500
|
+
throw error2;
|
|
2501
|
+
}
|
|
2502
|
+
const tasks = docs.map((_, index) => ({ index, snapshot: snapshots[index] }));
|
|
2503
|
+
await state.scheduler.map(tasks, async ({ index, snapshot }) => {
|
|
2504
|
+
let resolution;
|
|
2505
|
+
try {
|
|
2506
|
+
resolution = resolveCorrelatedFilterTemplate(parsedTemplate, snapshot);
|
|
2507
|
+
} catch (error2) {
|
|
2508
|
+
if (error2 instanceof CorrelatedReferenceError) {
|
|
2509
|
+
throw new ClientRequestError({
|
|
2510
|
+
success: false,
|
|
2511
|
+
kind: "error",
|
|
2512
|
+
code: "bad_request" /* BadRequest */,
|
|
2513
|
+
errors: [{ detail: error2.message }]
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
throw error2;
|
|
2517
|
+
}
|
|
2518
|
+
if (resolution.status === "unresolvable") {
|
|
2519
|
+
setDocValue(docs[index], include.path, null);
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
const expanded = resolution.filter;
|
|
2523
|
+
const operandErrors = validateExpandedCorrelatedOperands(expanded, this.getRequestComplexity());
|
|
2524
|
+
if (operandErrors.length > 0) {
|
|
2525
|
+
throw new ClientRequestError({
|
|
2526
|
+
success: false,
|
|
2527
|
+
kind: "error",
|
|
2528
|
+
code: "bad_request" /* BadRequest */,
|
|
2529
|
+
errors: [{ detail: operandErrors[0].detail }]
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
const authorized = await svc.genFilter("read", expanded);
|
|
2533
|
+
if (authorized === false) {
|
|
2534
|
+
throw new ClientRequestError({ success: false, kind: "error", code: "forbidden" /* Forbidden */ });
|
|
2535
|
+
}
|
|
2536
|
+
this.claimCorrelatedQuerySlot();
|
|
2537
|
+
let result;
|
|
2538
|
+
try {
|
|
2539
|
+
result = await svc.findOne(
|
|
2540
|
+
{},
|
|
2541
|
+
{
|
|
2542
|
+
select,
|
|
2543
|
+
sort,
|
|
2544
|
+
include: nestedInclude,
|
|
2545
|
+
overrides: { filter: authorized }
|
|
2546
|
+
},
|
|
2547
|
+
{ access: "read", lean: true, includePermissions: false }
|
|
2548
|
+
);
|
|
2549
|
+
} catch (error2) {
|
|
2550
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2551
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2552
|
+
throw error2;
|
|
2553
|
+
}
|
|
2554
|
+
if (result.success) {
|
|
2555
|
+
setDocValue(docs[index], include.path, result.data);
|
|
2556
|
+
} else if (result.code === "not_found" /* NotFound */) {
|
|
2557
|
+
setDocValue(docs[index], include.path, null);
|
|
2558
|
+
} else {
|
|
2559
|
+
throw new ClientRequestError(result);
|
|
2560
|
+
}
|
|
2561
|
+
});
|
|
2562
|
+
void depth;
|
|
2563
|
+
}
|
|
2564
|
+
async includeCorrelatedList(docs, include, snapshots, state, depth) {
|
|
2565
|
+
const svc = await this.getAuthorizedTargetService(include.model, "list");
|
|
2566
|
+
const { select, sort, skip, limit, page, pageSize, nestedInclude } = this.sanitizeCorrelatedArgs(include);
|
|
2567
|
+
const filterTemplate = include.filter;
|
|
2568
|
+
const templateErrors = this.validateClientFilter(filterTemplate);
|
|
2569
|
+
if (templateErrors.length > 0) {
|
|
2570
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, templateErrors[0]);
|
|
2571
|
+
}
|
|
2572
|
+
let parsedTemplate;
|
|
2573
|
+
try {
|
|
2574
|
+
parsedTemplate = await this.parseClientData(
|
|
2575
|
+
filterTemplate,
|
|
2576
|
+
state.scheduler
|
|
2577
|
+
);
|
|
2578
|
+
} catch (error2) {
|
|
2579
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2580
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2581
|
+
throw error2;
|
|
2582
|
+
}
|
|
2583
|
+
const tasks = docs.map((_, index) => ({ index, snapshot: snapshots[index] }));
|
|
2584
|
+
await state.scheduler.map(tasks, async ({ index, snapshot }) => {
|
|
2585
|
+
let resolution;
|
|
2586
|
+
try {
|
|
2587
|
+
resolution = resolveCorrelatedFilterTemplate(parsedTemplate, snapshot);
|
|
2588
|
+
} catch (error2) {
|
|
2589
|
+
if (error2 instanceof CorrelatedReferenceError) {
|
|
2590
|
+
throw new ClientRequestError({
|
|
2591
|
+
success: false,
|
|
2592
|
+
kind: "error",
|
|
2593
|
+
code: "bad_request" /* BadRequest */,
|
|
2594
|
+
errors: [{ detail: error2.message }]
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
throw error2;
|
|
2598
|
+
}
|
|
2599
|
+
if (resolution.status === "unresolvable") {
|
|
2600
|
+
setDocValue(docs[index], include.path, []);
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2603
|
+
const expanded = resolution.filter;
|
|
2604
|
+
const operandErrors = validateExpandedCorrelatedOperands(expanded, this.getRequestComplexity());
|
|
2605
|
+
if (operandErrors.length > 0) {
|
|
2606
|
+
throw new ClientRequestError({
|
|
2607
|
+
success: false,
|
|
2608
|
+
kind: "error",
|
|
2609
|
+
code: "bad_request" /* BadRequest */,
|
|
2610
|
+
errors: [{ detail: operandErrors[0].detail }]
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
const authorized = await svc.genFilter("list", expanded);
|
|
2614
|
+
if (authorized === false) {
|
|
2615
|
+
throw new ClientRequestError({ success: false, kind: "error", code: "forbidden" /* Forbidden */ });
|
|
2616
|
+
}
|
|
2617
|
+
this.claimCorrelatedQuerySlot();
|
|
2618
|
+
let result;
|
|
2619
|
+
try {
|
|
2620
|
+
result = await svc.find(
|
|
2621
|
+
{},
|
|
2622
|
+
{
|
|
2623
|
+
select,
|
|
2624
|
+
sort,
|
|
2625
|
+
skip,
|
|
2626
|
+
limit,
|
|
2627
|
+
page,
|
|
2628
|
+
pageSize,
|
|
2629
|
+
include: nestedInclude,
|
|
2630
|
+
overrides: { filter: authorized }
|
|
2631
|
+
},
|
|
2632
|
+
{ includeCount: false, lean: true, includePermissions: false }
|
|
2633
|
+
);
|
|
2634
|
+
} catch (error2) {
|
|
2635
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2636
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2637
|
+
throw error2;
|
|
2638
|
+
}
|
|
2639
|
+
if (result.success) {
|
|
2640
|
+
setDocValue(docs[index], include.path, result.data);
|
|
2641
|
+
} else {
|
|
2642
|
+
throw new ClientRequestError(result);
|
|
2643
|
+
}
|
|
2644
|
+
});
|
|
2645
|
+
void depth;
|
|
2646
|
+
}
|
|
2647
|
+
async includeCorrelatedCount(docs, include, snapshots, state, depth) {
|
|
2648
|
+
const svc = await this.getAuthorizedTargetService(include.model, "count");
|
|
2649
|
+
const filterTemplate = include.filter;
|
|
2650
|
+
const templateErrors = this.validateClientFilter(filterTemplate);
|
|
2651
|
+
if (templateErrors.length > 0) {
|
|
2652
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, templateErrors[0]);
|
|
2653
|
+
}
|
|
2654
|
+
const tasks = docs.map((_, index) => ({ index, snapshot: snapshots[index] }));
|
|
2655
|
+
await state.scheduler.map(tasks, async ({ index, snapshot }) => {
|
|
2656
|
+
let resolution;
|
|
2657
|
+
try {
|
|
2658
|
+
resolution = resolveCorrelatedFilterTemplate(filterTemplate, snapshot);
|
|
2659
|
+
} catch (error2) {
|
|
2660
|
+
if (error2 instanceof CorrelatedReferenceError) {
|
|
2661
|
+
throw new ClientRequestError({
|
|
2662
|
+
success: false,
|
|
2663
|
+
kind: "error",
|
|
2664
|
+
code: "bad_request" /* BadRequest */,
|
|
2665
|
+
errors: [{ detail: error2.message }]
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
throw error2;
|
|
2669
|
+
}
|
|
2670
|
+
if (resolution.status === "unresolvable") {
|
|
2671
|
+
setDocValue(docs[index], include.path, 0);
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
const expanded = resolution.filter;
|
|
2675
|
+
const operandErrors = validateExpandedCorrelatedOperands(expanded, this.getRequestComplexity());
|
|
2676
|
+
if (operandErrors.length > 0) {
|
|
2677
|
+
throw new ClientRequestError({
|
|
2678
|
+
success: false,
|
|
2679
|
+
kind: "error",
|
|
2680
|
+
code: "bad_request" /* BadRequest */,
|
|
2681
|
+
errors: [{ detail: operandErrors[0].detail }]
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
const authorized = await svc.genFilter("count", expanded);
|
|
2685
|
+
if (authorized === false) {
|
|
2686
|
+
throw new ClientRequestError({ success: false, kind: "error", code: "forbidden" /* Forbidden */ });
|
|
2687
|
+
}
|
|
2688
|
+
this.claimCorrelatedQuerySlot();
|
|
2689
|
+
let result;
|
|
2690
|
+
try {
|
|
2691
|
+
result = await svc.countTrusted(authorized);
|
|
2692
|
+
} catch (error2) {
|
|
2693
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2694
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2695
|
+
throw error2;
|
|
2696
|
+
}
|
|
2697
|
+
if (result.success) {
|
|
2698
|
+
setDocValue(docs[index], include.path, result.data);
|
|
2699
|
+
} else {
|
|
2700
|
+
throw new ClientRequestError(result);
|
|
2701
|
+
}
|
|
2702
|
+
});
|
|
2703
|
+
void depth;
|
|
2704
|
+
}
|
|
1815
2705
|
async parseClientData(filter2, scheduler = new RequestConcurrencyScheduler(this.getRequestComplexity().maxBulkConcurrency), scheduled = false) {
|
|
1816
2706
|
const result = await iterateQuery(
|
|
1817
2707
|
filter2,
|
|
@@ -1854,7 +2744,7 @@ var Base = class {
|
|
|
1854
2744
|
}
|
|
1855
2745
|
let ret = result.data;
|
|
1856
2746
|
if (sqOptions.path) {
|
|
1857
|
-
ret =
|
|
2747
|
+
ret = isArray7(ret) ? flatten(ret.map((v) => get4(v, sqOptions.path))) : get4(ret, sqOptions.path);
|
|
1858
2748
|
}
|
|
1859
2749
|
if (sqOptions.compact) {
|
|
1860
2750
|
ret = compact(castArray(ret));
|
|
@@ -1871,8 +2761,9 @@ var Base = class {
|
|
|
1871
2761
|
// src/services/service.ts
|
|
1872
2762
|
import {
|
|
1873
2763
|
castArray as castArray2,
|
|
2764
|
+
cloneDeep as cloneDeep3,
|
|
1874
2765
|
compact as compact2,
|
|
1875
|
-
isArray as
|
|
2766
|
+
isArray as isArray8,
|
|
1876
2767
|
isBoolean as isBoolean3,
|
|
1877
2768
|
isFunction as isFunction3,
|
|
1878
2769
|
omit,
|
|
@@ -2375,6 +3266,29 @@ var assertModelDocument = (value, modelName, hookName) => {
|
|
|
2375
3266
|
}
|
|
2376
3267
|
throw new Error(`${hookName} hook for model=${modelName} must return a Mongoose document instance`);
|
|
2377
3268
|
};
|
|
3269
|
+
var unsetDocPath = (doc, path) => {
|
|
3270
|
+
const segments = path.split(".");
|
|
3271
|
+
let current = doc;
|
|
3272
|
+
const unwrap = (value) => value != null && typeof value === "object" && "_doc" in value ? value._doc : value;
|
|
3273
|
+
current = unwrap(current);
|
|
3274
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
3275
|
+
if (current == null || typeof current !== "object") return;
|
|
3276
|
+
if (!Object.prototype.hasOwnProperty.call(current, segments[i])) return;
|
|
3277
|
+
current = unwrap(current[segments[i]]);
|
|
3278
|
+
}
|
|
3279
|
+
if (current != null && typeof current === "object") {
|
|
3280
|
+
delete current[segments[segments.length - 1]];
|
|
3281
|
+
}
|
|
3282
|
+
};
|
|
3283
|
+
var shouldKeepCorrelatedRef = (ref, keep) => {
|
|
3284
|
+
if (keep.has(ref)) return true;
|
|
3285
|
+
const top = ref.split(".")[0];
|
|
3286
|
+
if (keep.has(top)) return true;
|
|
3287
|
+
for (const kept of keep) {
|
|
3288
|
+
if (kept === ref || kept.startsWith(`${ref}.`) || ref.startsWith(`${kept}.`)) return true;
|
|
3289
|
+
}
|
|
3290
|
+
return false;
|
|
3291
|
+
};
|
|
2378
3292
|
var Service = class extends Base {
|
|
2379
3293
|
findRawParentDoc(args) {
|
|
2380
3294
|
return this.model.findOne({
|
|
@@ -2441,8 +3355,9 @@ var Service = class extends Base {
|
|
|
2441
3355
|
overridePopulate || this.genPopulate(populateAccess || access, populate),
|
|
2442
3356
|
this.genAllowedFields({}, access, this.baseFieldsExt)
|
|
2443
3357
|
]);
|
|
2444
|
-
const { includes, includeLocalFields, includePaths } = this.processInclude(include);
|
|
2445
|
-
const
|
|
3358
|
+
const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } = this.processInclude(include);
|
|
3359
|
+
const correlatedOutputPaths = correlatedIncludes.map((entry) => entry.path);
|
|
3360
|
+
const finalSelect = normalizeSelect(_select).concat(includeLocalFields, correlatedReferenceFields);
|
|
2446
3361
|
const query = {
|
|
2447
3362
|
filter: _filter,
|
|
2448
3363
|
select: finalSelect,
|
|
@@ -2475,8 +3390,14 @@ var Service = class extends Base {
|
|
|
2475
3390
|
originalDocumentSnapshot: toObject(doc),
|
|
2476
3391
|
resolvedQuery: query
|
|
2477
3392
|
};
|
|
3393
|
+
const correlatedSnapshots = [cloneDeep3(toObject(doc))];
|
|
2478
3394
|
try {
|
|
2479
3395
|
doc = await this.includeDocs(doc, includes);
|
|
3396
|
+
if (correlatedIncludes.length > 0) {
|
|
3397
|
+
const docList = [doc];
|
|
3398
|
+
await this.includeCorrelatedDocs(docList, correlatedIncludes, correlatedSnapshots);
|
|
3399
|
+
doc = docList[0];
|
|
3400
|
+
}
|
|
2480
3401
|
} catch (error2) {
|
|
2481
3402
|
const result = this.getClientRequestErrorResult(error2);
|
|
2482
3403
|
if (result) {
|
|
@@ -2494,8 +3415,20 @@ var Service = class extends Base {
|
|
|
2494
3415
|
doc = await this.trimOutputFields(
|
|
2495
3416
|
doc,
|
|
2496
3417
|
access,
|
|
2497
|
-
this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
|
|
3418
|
+
this.baseFieldsExt.concat(includePaths, correlatedOutputPaths, normalizeSelect(overrideSelect))
|
|
2498
3419
|
);
|
|
3420
|
+
if (correlatedReferenceFields.length > 0) {
|
|
3421
|
+
const keep = /* @__PURE__ */ new Set([
|
|
3422
|
+
...normalizeSelect(_select),
|
|
3423
|
+
...normalizeSelect(overrideSelect),
|
|
3424
|
+
...this.baseFieldsExt,
|
|
3425
|
+
...includePaths,
|
|
3426
|
+
...correlatedOutputPaths
|
|
3427
|
+
]);
|
|
3428
|
+
for (const ref of correlatedReferenceFields) {
|
|
3429
|
+
if (!shouldKeepCorrelatedRef(ref, keep)) unsetDocPath(doc, ref);
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
2499
3432
|
if (!includePermissions) doc = this.addEmptyPermissions(doc);
|
|
2500
3433
|
this.completeOp("findOne", startedAt, "success" /* Success */, _filter);
|
|
2501
3434
|
return { success: true, kind: "single", code: "success" /* Success */, data: doc, query, context };
|
|
@@ -2541,11 +3474,12 @@ var Service = class extends Base {
|
|
|
2541
3474
|
this.genAllowedFields({}, "list", this.baseFieldsExt)
|
|
2542
3475
|
]);
|
|
2543
3476
|
const finalSelect = normalizeSelect(_select);
|
|
2544
|
-
const filteredPopulate =
|
|
2545
|
-
const { includes, includeLocalFields, includePaths } = this.processInclude(include);
|
|
3477
|
+
const filteredPopulate = isArray8(finalSelect) && isArray8(_populate) ? _populate.filter((p) => finalSelect.includes(p.path.split(".")[0])) : _populate;
|
|
3478
|
+
const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } = this.processInclude(include);
|
|
3479
|
+
const correlatedOutputPaths = correlatedIncludes.map((entry) => entry.path);
|
|
2546
3480
|
const query = {
|
|
2547
3481
|
filter: _filter,
|
|
2548
|
-
select: finalSelect.concat(includeLocalFields),
|
|
3482
|
+
select: finalSelect.concat(includeLocalFields, correlatedReferenceFields),
|
|
2549
3483
|
populate: filteredPopulate,
|
|
2550
3484
|
sort,
|
|
2551
3485
|
...pagination
|
|
@@ -2554,7 +3488,7 @@ var Service = class extends Base {
|
|
|
2554
3488
|
sort,
|
|
2555
3489
|
skip: pagination.skip,
|
|
2556
3490
|
limit: pagination.limit,
|
|
2557
|
-
selectCount: finalSelect.concat(includeLocalFields).length,
|
|
3491
|
+
selectCount: finalSelect.concat(includeLocalFields, correlatedReferenceFields).length,
|
|
2558
3492
|
populateCount: Array.isArray(filteredPopulate) ? filteredPopulate.length : filteredPopulate ? 1 : 0
|
|
2559
3493
|
});
|
|
2560
3494
|
if (_filter === false) {
|
|
@@ -2579,8 +3513,12 @@ var Service = class extends Base {
|
|
|
2579
3513
|
resolvedQuery: query
|
|
2580
3514
|
}));
|
|
2581
3515
|
const _decorate = isFunction3(decorate) ? decorate : (v) => v;
|
|
3516
|
+
const correlatedSnapshots = docs.map((doc) => cloneDeep3(toObject(doc)));
|
|
2582
3517
|
try {
|
|
2583
3518
|
docs = await this.includeDocs(docs, includes);
|
|
3519
|
+
if (correlatedIncludes.length > 0) {
|
|
3520
|
+
docs = await this.includeCorrelatedDocs(docs, correlatedIncludes, correlatedSnapshots);
|
|
3521
|
+
}
|
|
2584
3522
|
} catch (error2) {
|
|
2585
3523
|
const result = this.getClientRequestErrorResult(error2);
|
|
2586
3524
|
if (result) {
|
|
@@ -2602,8 +3540,20 @@ var Service = class extends Base {
|
|
|
2602
3540
|
doc = await this.trimOutputFields(
|
|
2603
3541
|
doc,
|
|
2604
3542
|
"list",
|
|
2605
|
-
this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
|
|
3543
|
+
this.baseFieldsExt.concat(includePaths, correlatedOutputPaths, normalizeSelect(overrideSelect))
|
|
2606
3544
|
);
|
|
3545
|
+
if (correlatedReferenceFields.length > 0) {
|
|
3546
|
+
const keep = /* @__PURE__ */ new Set([
|
|
3547
|
+
...normalizeSelect(_select),
|
|
3548
|
+
...normalizeSelect(overrideSelect),
|
|
3549
|
+
...this.baseFieldsExt,
|
|
3550
|
+
...includePaths,
|
|
3551
|
+
...correlatedOutputPaths
|
|
3552
|
+
]);
|
|
3553
|
+
for (const ref of correlatedReferenceFields) {
|
|
3554
|
+
if (!shouldKeepCorrelatedRef(ref, keep)) unsetDocPath(doc, ref);
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
2607
3557
|
doc = await _decorate(doc, contexts[i]);
|
|
2608
3558
|
if (!includePermissions) doc = this.addEmptyPermissions(doc);
|
|
2609
3559
|
return doc;
|
|
@@ -2688,7 +3638,7 @@ var Service = class extends Base {
|
|
|
2688
3638
|
validationErrors.push({ index, errors: [] });
|
|
2689
3639
|
return void 0;
|
|
2690
3640
|
}
|
|
2691
|
-
} else if (
|
|
3641
|
+
} else if (isArray8(validated)) {
|
|
2692
3642
|
if (validated.length > 0) {
|
|
2693
3643
|
validationErrors.push({
|
|
2694
3644
|
index,
|
|
@@ -2845,7 +3795,7 @@ var Service = class extends Base {
|
|
|
2845
3795
|
this.completeOp("updateOne", startedAt, "bad_request" /* BadRequest */, _filter);
|
|
2846
3796
|
return { success: false, kind: "error", code: "bad_request" /* BadRequest */ };
|
|
2847
3797
|
}
|
|
2848
|
-
} else if (
|
|
3798
|
+
} else if (isArray8(validated)) {
|
|
2849
3799
|
if (validated.length > 0) {
|
|
2850
3800
|
this.completeOp("updateOne", startedAt, "bad_request" /* BadRequest */, _filter);
|
|
2851
3801
|
return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: validated };
|
|
@@ -3032,6 +3982,23 @@ var Service = class extends Base {
|
|
|
3032
3982
|
if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
3033
3983
|
return { success: true, kind: "single", code: "success" /* Success */, data: await this.model.countDocuments(filter2), query };
|
|
3034
3984
|
}
|
|
3985
|
+
/**
|
|
3986
|
+
* Trusted count for correlated includes (ACI-03): the caller already
|
|
3987
|
+
* resolved parent references, revalidated expanded operands, and applied
|
|
3988
|
+
* explicit `count` access via `genFilter`. This skips client-filter
|
|
3989
|
+
* validation and subquery parsing so substituted parent data stays inert.
|
|
3990
|
+
*/
|
|
3991
|
+
async countTrusted(authorizedFilter) {
|
|
3992
|
+
const query = { filter: authorizedFilter };
|
|
3993
|
+
if (authorizedFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
3994
|
+
return {
|
|
3995
|
+
success: true,
|
|
3996
|
+
kind: "single",
|
|
3997
|
+
code: "success" /* Success */,
|
|
3998
|
+
data: await this.model.countDocuments(authorizedFilter),
|
|
3999
|
+
query
|
|
4000
|
+
};
|
|
4001
|
+
}
|
|
3035
4002
|
async countByFieldValues(foreignField, values, filter2 = {}, access = "list") {
|
|
3036
4003
|
if (!isValidFieldPath(foreignField)) {
|
|
3037
4004
|
return {
|
|
@@ -3436,7 +4403,7 @@ var DataService = class {
|
|
|
3436
4403
|
this.req = req;
|
|
3437
4404
|
this.dataName = dataName;
|
|
3438
4405
|
this.options = getDataOptions(dataName);
|
|
3439
|
-
this.data =
|
|
4406
|
+
this.data = getDataSnapshot(dataName);
|
|
3440
4407
|
}
|
|
3441
4408
|
async findOne(filter2, args, options) {
|
|
3442
4409
|
const filterErrors = validateClientFilter(filter2);
|
|
@@ -3547,29 +4514,64 @@ var DataService = class {
|
|
|
3547
4514
|
};
|
|
3548
4515
|
|
|
3549
4516
|
// src/processors.ts
|
|
3550
|
-
import { cloneDeep, forEach as forEach6, get as get7, isArray as
|
|
4517
|
+
import { cloneDeep as cloneDeep4, forEach as forEach6, get as get7, hasOwn, isArray as isArray9, isPlainObject as isPlainObject14, map, set as set5 } from "@web-ts-toolkit/utils";
|
|
3551
4518
|
var UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
3552
|
-
|
|
4519
|
+
var DEFAULT_MAX_SLICE = 1e3;
|
|
4520
|
+
var DEFAULT_MASK_REPLACEMENT = "***";
|
|
4521
|
+
function failUnsafeSegment(path, fn = "copyAndDepopulate") {
|
|
3553
4522
|
throw new Error(
|
|
3554
|
-
|
|
4523
|
+
`${fn}: refusing path '${path}' because it contains an unsafe segment (__proto__, prototype, or constructor).`
|
|
3555
4524
|
);
|
|
3556
4525
|
}
|
|
3557
4526
|
function failMissingId(path) {
|
|
3558
4527
|
throw new Error(`copyAndDepopulate: path '${path}' resolved to a record missing the configured id field.`);
|
|
3559
4528
|
}
|
|
3560
|
-
function
|
|
4529
|
+
function failInvalidSlice(value, field, fn) {
|
|
4530
|
+
throw new Error(`${fn}: '${field}' must be a non-negative safe integer, received ${String(value)}.`);
|
|
4531
|
+
}
|
|
4532
|
+
function assertSafePath(path, fn = "copyAndDepopulate") {
|
|
3561
4533
|
const segs = path.split(".");
|
|
3562
4534
|
for (const seg of segs) {
|
|
3563
4535
|
if (seg.length === 0) continue;
|
|
3564
4536
|
if (UNSAFE_PATH_KEYS.has(seg)) {
|
|
3565
|
-
failUnsafeSegment(path);
|
|
4537
|
+
failUnsafeSegment(path, fn);
|
|
3566
4538
|
}
|
|
3567
4539
|
}
|
|
3568
4540
|
}
|
|
4541
|
+
function assertNonNegativeSafeInteger(value, field, fn) {
|
|
4542
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
4543
|
+
failInvalidSlice(value, field, fn);
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
function resolveLeafParents(obj, src) {
|
|
4547
|
+
const segs = src.split(".");
|
|
4548
|
+
let targets = [obj];
|
|
4549
|
+
for (let ind = 0; ind < segs.length - 1; ind += 1) {
|
|
4550
|
+
const seg = segs[ind];
|
|
4551
|
+
targets = targets.reduce((ret, target) => {
|
|
4552
|
+
if (!isPlainObject14(target)) return ret;
|
|
4553
|
+
const targetObject = target;
|
|
4554
|
+
const next = targetObject[seg];
|
|
4555
|
+
if (isArray9(next)) {
|
|
4556
|
+
for (const item of next) {
|
|
4557
|
+
ret.push(item);
|
|
4558
|
+
}
|
|
4559
|
+
} else if (next !== null && next !== void 0) ret.push(next);
|
|
4560
|
+
return ret;
|
|
4561
|
+
}, []);
|
|
4562
|
+
}
|
|
4563
|
+
const leaf = segs[segs.length - 1];
|
|
4564
|
+
const pairs = [];
|
|
4565
|
+
forEach6(targets, (target) => {
|
|
4566
|
+
if (!isPlainObject14(target)) return;
|
|
4567
|
+
pairs.push({ parent: target, leaf });
|
|
4568
|
+
});
|
|
4569
|
+
return pairs;
|
|
4570
|
+
}
|
|
3569
4571
|
var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
|
|
3570
4572
|
const mutable = options.mutable !== false;
|
|
3571
4573
|
const idField = options.idField ?? "_id";
|
|
3572
|
-
const obj = mutable ? docObject :
|
|
4574
|
+
const obj = mutable ? docObject : cloneDeep4(docObject);
|
|
3573
4575
|
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
3574
4576
|
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
3575
4577
|
const src = op.src;
|
|
@@ -3582,15 +4584,15 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
3582
4584
|
forEach6(segs, (seg, ind) => {
|
|
3583
4585
|
if (segs.length === ind + 1) {
|
|
3584
4586
|
forEach6(targets, (target) => {
|
|
3585
|
-
if (!
|
|
4587
|
+
if (!isPlainObject14(target)) return;
|
|
3586
4588
|
const targetObject = target;
|
|
3587
4589
|
const leaf = targetObject[seg];
|
|
3588
4590
|
const sourceForDest = leaf;
|
|
3589
4591
|
let depopulated;
|
|
3590
|
-
if (
|
|
4592
|
+
if (isArray9(leaf)) {
|
|
3591
4593
|
if (leaf.length === 0) {
|
|
3592
4594
|
depopulated = [];
|
|
3593
|
-
} else if (leaf.every((item) =>
|
|
4595
|
+
} else if (leaf.every((item) => isPlainObject14(item))) {
|
|
3594
4596
|
depopulated = map(leaf, idField).map((id, i) => {
|
|
3595
4597
|
if (id === void 0) {
|
|
3596
4598
|
failMissingId(`${src}[${i}]`);
|
|
@@ -3600,7 +4602,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
3600
4602
|
} else {
|
|
3601
4603
|
return;
|
|
3602
4604
|
}
|
|
3603
|
-
} else if (
|
|
4605
|
+
} else if (isPlainObject14(leaf)) {
|
|
3604
4606
|
const id = get7(leaf, idField);
|
|
3605
4607
|
if (id === void 0) {
|
|
3606
4608
|
failMissingId(src);
|
|
@@ -3614,11 +4616,14 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
3614
4616
|
});
|
|
3615
4617
|
} else {
|
|
3616
4618
|
targets = targets.reduce((ret, target) => {
|
|
3617
|
-
if (!
|
|
4619
|
+
if (!isPlainObject14(target)) return ret;
|
|
3618
4620
|
const targetObject = target;
|
|
3619
4621
|
const next = targetObject[seg];
|
|
3620
|
-
if (
|
|
3621
|
-
|
|
4622
|
+
if (isArray9(next)) {
|
|
4623
|
+
for (const item of next) {
|
|
4624
|
+
ret.push(item);
|
|
4625
|
+
}
|
|
4626
|
+
} else if (next !== null && next !== void 0) ret.push(next);
|
|
3622
4627
|
return ret;
|
|
3623
4628
|
}, []);
|
|
3624
4629
|
}
|
|
@@ -3626,6 +4631,125 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
3626
4631
|
});
|
|
3627
4632
|
return obj;
|
|
3628
4633
|
};
|
|
4634
|
+
var copyPaths = (docObject, operations, options = { mutable: true }) => {
|
|
4635
|
+
const mutable = options.mutable !== false;
|
|
4636
|
+
const obj = mutable ? docObject : cloneDeep4(docObject);
|
|
4637
|
+
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4638
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
4639
|
+
const src = op.src;
|
|
4640
|
+
const dest = op.dest;
|
|
4641
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
4642
|
+
assertSafePath(src, "copyPaths");
|
|
4643
|
+
assertSafePath(dest, "copyPaths");
|
|
4644
|
+
if (src === dest) return;
|
|
4645
|
+
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4646
|
+
if (!hasOwn(parent, leaf)) return;
|
|
4647
|
+
set5(parent, dest, cloneDeep4(parent[leaf]));
|
|
4648
|
+
});
|
|
4649
|
+
});
|
|
4650
|
+
return obj;
|
|
4651
|
+
};
|
|
4652
|
+
var movePaths = (docObject, operations, options = { mutable: true }) => {
|
|
4653
|
+
const mutable = options.mutable !== false;
|
|
4654
|
+
const obj = mutable ? docObject : cloneDeep4(docObject);
|
|
4655
|
+
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4656
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
4657
|
+
const src = op.src;
|
|
4658
|
+
const dest = op.dest;
|
|
4659
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
4660
|
+
assertSafePath(src, "movePaths");
|
|
4661
|
+
assertSafePath(dest, "movePaths");
|
|
4662
|
+
if (src === dest) return;
|
|
4663
|
+
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4664
|
+
if (!hasOwn(parent, leaf)) return;
|
|
4665
|
+
const value = cloneDeep4(parent[leaf]);
|
|
4666
|
+
set5(parent, dest, value);
|
|
4667
|
+
if (dest === leaf || dest.startsWith(`${leaf}.`)) return;
|
|
4668
|
+
delete parent[leaf];
|
|
4669
|
+
});
|
|
4670
|
+
});
|
|
4671
|
+
return obj;
|
|
4672
|
+
};
|
|
4673
|
+
var sliceArrays = (docObject, operations, options = { mutable: true, maxSlice: DEFAULT_MAX_SLICE }) => {
|
|
4674
|
+
const mutable = options.mutable !== false;
|
|
4675
|
+
const maxSlice = options.maxSlice ?? DEFAULT_MAX_SLICE;
|
|
4676
|
+
assertNonNegativeSafeInteger(maxSlice, "maxSlice", "sliceArrays");
|
|
4677
|
+
const obj = mutable ? docObject : cloneDeep4(docObject);
|
|
4678
|
+
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4679
|
+
if (!op || typeof op.src !== "string") return;
|
|
4680
|
+
const src = op.src;
|
|
4681
|
+
if (src.length === 0) return;
|
|
4682
|
+
assertSafePath(src, "sliceArrays");
|
|
4683
|
+
const rawDest = typeof op.dest === "undefined" ? void 0 : op.dest;
|
|
4684
|
+
if (typeof rawDest !== "undefined") {
|
|
4685
|
+
if (typeof rawDest !== "string" || rawDest.length === 0) return;
|
|
4686
|
+
assertSafePath(rawDest, "sliceArrays");
|
|
4687
|
+
}
|
|
4688
|
+
const skip = op.skip ?? 0;
|
|
4689
|
+
assertNonNegativeSafeInteger(skip, "skip", "sliceArrays");
|
|
4690
|
+
let limit;
|
|
4691
|
+
if (typeof op.limit !== "undefined") {
|
|
4692
|
+
assertNonNegativeSafeInteger(op.limit, "limit", "sliceArrays");
|
|
4693
|
+
limit = Math.min(op.limit, maxSlice);
|
|
4694
|
+
}
|
|
4695
|
+
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4696
|
+
const leafValue = parent[leaf];
|
|
4697
|
+
if (!isArray9(leafValue)) return;
|
|
4698
|
+
const end = typeof limit === "undefined" ? void 0 : skip + limit;
|
|
4699
|
+
const window = leafValue.slice(skip, end);
|
|
4700
|
+
if (typeof rawDest === "undefined") {
|
|
4701
|
+
set5(parent, leaf, window);
|
|
4702
|
+
} else {
|
|
4703
|
+
set5(parent, rawDest, window);
|
|
4704
|
+
}
|
|
4705
|
+
});
|
|
4706
|
+
});
|
|
4707
|
+
return obj;
|
|
4708
|
+
};
|
|
4709
|
+
var countPaths = (docObject, operations, options = { mutable: true }) => {
|
|
4710
|
+
const mutable = options.mutable !== false;
|
|
4711
|
+
const obj = mutable ? docObject : cloneDeep4(docObject);
|
|
4712
|
+
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4713
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
4714
|
+
const src = op.src;
|
|
4715
|
+
const dest = op.dest;
|
|
4716
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
4717
|
+
assertSafePath(src, "countPaths");
|
|
4718
|
+
assertSafePath(dest, "countPaths");
|
|
4719
|
+
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4720
|
+
if (!hasOwn(parent, leaf)) return;
|
|
4721
|
+
const leafValue = parent[leaf];
|
|
4722
|
+
let count;
|
|
4723
|
+
if (isArray9(leafValue)) {
|
|
4724
|
+
count = leafValue.length;
|
|
4725
|
+
} else if (isPlainObject14(leafValue)) {
|
|
4726
|
+
count = Object.keys(leafValue).length;
|
|
4727
|
+
} else if (leafValue === null || typeof leafValue === "undefined") {
|
|
4728
|
+
count = 0;
|
|
4729
|
+
} else {
|
|
4730
|
+
count = 1;
|
|
4731
|
+
}
|
|
4732
|
+
set5(parent, dest, count);
|
|
4733
|
+
});
|
|
4734
|
+
});
|
|
4735
|
+
return obj;
|
|
4736
|
+
};
|
|
4737
|
+
var maskPaths = (docObject, operations, options = { mutable: true }) => {
|
|
4738
|
+
const mutable = options.mutable !== false;
|
|
4739
|
+
const obj = mutable ? docObject : cloneDeep4(docObject);
|
|
4740
|
+
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4741
|
+
if (!op || typeof op.src !== "string") return;
|
|
4742
|
+
const src = op.src;
|
|
4743
|
+
if (src.length === 0) return;
|
|
4744
|
+
assertSafePath(src, "maskPaths");
|
|
4745
|
+
const replacement = Object.prototype.hasOwnProperty.call(op, "replacement") ? op.replacement : DEFAULT_MASK_REPLACEMENT;
|
|
4746
|
+
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4747
|
+
if (!hasOwn(parent, leaf)) return;
|
|
4748
|
+
set5(parent, leaf, replacement);
|
|
4749
|
+
});
|
|
4750
|
+
});
|
|
4751
|
+
return obj;
|
|
4752
|
+
};
|
|
3629
4753
|
|
|
3630
4754
|
// src/symbols.ts
|
|
3631
4755
|
var MIDDLEWARE = /* @__PURE__ */ Symbol.for("@web-ts-toolkit/access-router/middleware");
|
|
@@ -3668,18 +4792,18 @@ var Cache = class {
|
|
|
3668
4792
|
import {
|
|
3669
4793
|
castArray as castArray3,
|
|
3670
4794
|
arrayToRecord,
|
|
3671
|
-
hasOwn as
|
|
3672
|
-
isArray as
|
|
4795
|
+
hasOwn as hasOwn3,
|
|
4796
|
+
isArray as isArray10,
|
|
3673
4797
|
isBoolean as isBoolean4,
|
|
3674
4798
|
isEmpty as isEmpty2,
|
|
3675
4799
|
isEqual,
|
|
3676
4800
|
isFunction as isFunction4,
|
|
3677
|
-
isPlainObject as
|
|
4801
|
+
isPlainObject as isPlainObject15,
|
|
3678
4802
|
isString as isString5
|
|
3679
4803
|
} from "@web-ts-toolkit/utils";
|
|
3680
4804
|
|
|
3681
4805
|
// src/permission.ts
|
|
3682
|
-
import { hasOwn } from "@web-ts-toolkit/utils";
|
|
4806
|
+
import { hasOwn as hasOwn2 } from "@web-ts-toolkit/utils";
|
|
3683
4807
|
var Permission = class {
|
|
3684
4808
|
constructor(permissions) {
|
|
3685
4809
|
this.permissions = { ...permissions };
|
|
@@ -3695,7 +4819,7 @@ var Permission = class {
|
|
|
3695
4819
|
return [permissionOrPermissions].concat(permissions);
|
|
3696
4820
|
}
|
|
3697
4821
|
hasKey(permission) {
|
|
3698
|
-
return
|
|
4822
|
+
return hasOwn2(this.permissions, permission);
|
|
3699
4823
|
}
|
|
3700
4824
|
has(permission) {
|
|
3701
4825
|
return this.permissions[permission] === true;
|
|
@@ -3715,10 +4839,10 @@ var permission_default = Permission;
|
|
|
3715
4839
|
|
|
3716
4840
|
// src/core-shared.ts
|
|
3717
4841
|
function isAndFilter(filter2) {
|
|
3718
|
-
return !!filter2 &&
|
|
4842
|
+
return !!filter2 && isPlainObject15(filter2) && Object.keys(filter2).length === 1 && isArray10(filter2.$and);
|
|
3719
4843
|
}
|
|
3720
4844
|
function isMergeableClause(filter2) {
|
|
3721
|
-
return !!filter2 &&
|
|
4845
|
+
return !!filter2 && isPlainObject15(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
|
|
3722
4846
|
}
|
|
3723
4847
|
function optimizeAndFilter(clausesInput) {
|
|
3724
4848
|
const clauses = [];
|
|
@@ -3733,10 +4857,10 @@ function optimizeAndFilter(clausesInput) {
|
|
|
3733
4857
|
}
|
|
3734
4858
|
}
|
|
3735
4859
|
const dedupeKey = (value) => {
|
|
3736
|
-
if (
|
|
4860
|
+
if (isArray10(value)) {
|
|
3737
4861
|
return `[${value.map(dedupeKey).join(",")}]`;
|
|
3738
4862
|
}
|
|
3739
|
-
if (
|
|
4863
|
+
if (isPlainObject15(value)) {
|
|
3740
4864
|
return `{${Object.keys(value).sort().map((key) => `${key}:${dedupeKey(value[key])}`).join(",")}}`;
|
|
3741
4865
|
}
|
|
3742
4866
|
return JSON.stringify(value);
|
|
@@ -3760,7 +4884,7 @@ function optimizeAndFilter(clausesInput) {
|
|
|
3760
4884
|
}
|
|
3761
4885
|
let canMerge = true;
|
|
3762
4886
|
for (const [key, value] of Object.entries(clause)) {
|
|
3763
|
-
if (
|
|
4887
|
+
if (hasOwn3(mergedClause, key) && !isEqual(mergedClause[key], value)) {
|
|
3764
4888
|
canMerge = false;
|
|
3765
4889
|
break;
|
|
3766
4890
|
}
|
|
@@ -3778,7 +4902,7 @@ function optimizeAndFilter(clausesInput) {
|
|
|
3778
4902
|
}
|
|
3779
4903
|
function normalizeFilter(filter2) {
|
|
3780
4904
|
if (filter2 === false) return false;
|
|
3781
|
-
if (!filter2 || !
|
|
4905
|
+
if (!filter2 || !isPlainObject15(filter2) || isEmpty2(filter2)) return null;
|
|
3782
4906
|
if (!isAndFilter(filter2)) {
|
|
3783
4907
|
return filter2;
|
|
3784
4908
|
}
|
|
@@ -3804,11 +4928,12 @@ async function resolveAccessFilter({
|
|
|
3804
4928
|
}) {
|
|
3805
4929
|
let nextFilter = normalizeFilter(filter2);
|
|
3806
4930
|
const overrideFilterFn = getOption(`overrideFilter.${access}`, null);
|
|
3807
|
-
if (isFunction4(overrideFilterFn)) {
|
|
4931
|
+
if (nextFilter !== false && isFunction4(overrideFilterFn)) {
|
|
3808
4932
|
nextFilter = normalizeFilter(await overrideFilterFn.call(req, nextFilter, permissions));
|
|
3809
4933
|
}
|
|
4934
|
+
if (nextFilter === false) return false;
|
|
3810
4935
|
const baseFilterFn = getOption(`baseFilter.${access}`, null);
|
|
3811
|
-
if (!isFunction4(baseFilterFn)) return nextFilter
|
|
4936
|
+
if (!isFunction4(baseFilterFn)) return nextFilter ?? {};
|
|
3812
4937
|
const baseFilter = normalizeFilter(
|
|
3813
4938
|
cache.has(cacheKey) ? cache.get(cacheKey) : await baseFilterFn.call(req, permissions)
|
|
3814
4939
|
);
|
|
@@ -3816,7 +4941,7 @@ async function resolveAccessFilter({
|
|
|
3816
4941
|
cache.set(cacheKey, baseFilter);
|
|
3817
4942
|
}
|
|
3818
4943
|
if (baseFilter === false) return false;
|
|
3819
|
-
if (!baseFilter) return nextFilter
|
|
4944
|
+
if (!baseFilter) return nextFilter ?? {};
|
|
3820
4945
|
if (!nextFilter) return baseFilter;
|
|
3821
4946
|
return optimizeAndFilter([baseFilter, nextFilter]);
|
|
3822
4947
|
}
|
|
@@ -3824,15 +4949,30 @@ function getRequestPermissions(req) {
|
|
|
3824
4949
|
const requestPermissionField = getGlobalOption("requestPermissionField");
|
|
3825
4950
|
return new permission_default(req[requestPermissionField] || {});
|
|
3826
4951
|
}
|
|
4952
|
+
var permissionFieldOwners = /* @__PURE__ */ new WeakMap();
|
|
4953
|
+
function getPermissionFieldOwnerMap(req) {
|
|
4954
|
+
let owners = permissionFieldOwners.get(req);
|
|
4955
|
+
if (!owners) {
|
|
4956
|
+
owners = /* @__PURE__ */ new Map();
|
|
4957
|
+
permissionFieldOwners.set(req, owners);
|
|
4958
|
+
}
|
|
4959
|
+
return owners;
|
|
4960
|
+
}
|
|
3827
4961
|
async function setRequestPermissions(req) {
|
|
3828
4962
|
const requestPermissionField = getGlobalOption("requestPermissionField");
|
|
3829
|
-
|
|
4963
|
+
const activeRuntime = getActiveRuntime();
|
|
4964
|
+
const owners = getPermissionFieldOwnerMap(req);
|
|
4965
|
+
const owner = owners.get(requestPermissionField);
|
|
4966
|
+
const hasFieldValue = Boolean(req[requestPermissionField]);
|
|
4967
|
+
if (owner === activeRuntime) return;
|
|
4968
|
+
if (hasFieldValue && owner === void 0) return;
|
|
3830
4969
|
const globalPermissions = getGlobalOption("globalPermissions");
|
|
3831
4970
|
if (!isFunction4(globalPermissions)) return;
|
|
3832
4971
|
const permissions = await globalPermissions.call(req, req);
|
|
3833
|
-
if (
|
|
3834
|
-
else if (
|
|
4972
|
+
if (isPlainObject15(permissions)) req[requestPermissionField] = permissions;
|
|
4973
|
+
else if (isArray10(permissions)) req[requestPermissionField] = arrayToRecord(permissions);
|
|
3835
4974
|
else if (isString5(permissions)) req[requestPermissionField] = { [permissions]: true };
|
|
4975
|
+
owners.set(requestPermissionField, activeRuntime);
|
|
3836
4976
|
}
|
|
3837
4977
|
async function evaluateRouteGuard(req, permissions, routeGuard) {
|
|
3838
4978
|
const phas = (key) => permissions.has(key);
|
|
@@ -3843,7 +4983,7 @@ async function evaluateRouteGuard(req, permissions, routeGuard) {
|
|
|
3843
4983
|
if (isString5(routeGuard)) {
|
|
3844
4984
|
return stringHandler(routeGuard);
|
|
3845
4985
|
}
|
|
3846
|
-
if (
|
|
4986
|
+
if (isArray10(routeGuard)) {
|
|
3847
4987
|
return arrayHandler(routeGuard);
|
|
3848
4988
|
}
|
|
3849
4989
|
if (isFunction4(routeGuard)) {
|
|
@@ -3881,7 +5021,7 @@ async function collectSchemaFields({
|
|
|
3881
5021
|
if (value) fields.push(key);
|
|
3882
5022
|
} else if (isString5(value)) {
|
|
3883
5023
|
if (stringHandler(value)) fields.push(key);
|
|
3884
|
-
} else if (
|
|
5024
|
+
} else if (isArray10(value)) {
|
|
3885
5025
|
if (arrayHandler(value)) fields.push(key);
|
|
3886
5026
|
} else if (isFunction4(value)) {
|
|
3887
5027
|
if (await value.call(req, ...functionArgs)) fields.push(key);
|
|
@@ -3900,17 +5040,14 @@ function getResolvedRequestPermissions(req) {
|
|
|
3900
5040
|
async function setResolvedRequestPermissions(req) {
|
|
3901
5041
|
await setRequestPermissions(req);
|
|
3902
5042
|
}
|
|
3903
|
-
async function canActivateRequest(req, routeGuard) {
|
|
3904
|
-
const permissions = getGlobalPermissions(req);
|
|
3905
|
-
return evaluateRouteGuard(req, permissions, routeGuard);
|
|
3906
|
-
}
|
|
3907
5043
|
async function initializeAclRequest({
|
|
3908
5044
|
req,
|
|
3909
5045
|
flag,
|
|
5046
|
+
runtime,
|
|
3910
5047
|
createCore,
|
|
3911
5048
|
assignCore
|
|
3912
5049
|
}) {
|
|
3913
|
-
if (req[flag]) {
|
|
5050
|
+
if (req[flag] === runtime) {
|
|
3914
5051
|
return null;
|
|
3915
5052
|
}
|
|
3916
5053
|
const core = createCore(req);
|
|
@@ -3918,7 +5055,7 @@ async function initializeAclRequest({
|
|
|
3918
5055
|
assignCore(core);
|
|
3919
5056
|
req[PERMISSIONS] = core.getPermissions();
|
|
3920
5057
|
req[PERMISSION_KEYS] = [...req[PERMISSIONS].keys];
|
|
3921
|
-
req[flag] =
|
|
5058
|
+
req[flag] = runtime;
|
|
3922
5059
|
return core;
|
|
3923
5060
|
}
|
|
3924
5061
|
|
|
@@ -4054,6 +5191,7 @@ async function resolveSelectForRequest({
|
|
|
4054
5191
|
// src/core.ts
|
|
4055
5192
|
var Core = class {
|
|
4056
5193
|
constructor(req) {
|
|
5194
|
+
this.cachedPermissions = null;
|
|
4057
5195
|
this.req = req;
|
|
4058
5196
|
this.caches = {
|
|
4059
5197
|
baseFilter: new Cache()
|
|
@@ -4188,7 +5326,7 @@ var Core = class {
|
|
|
4188
5326
|
if (isFunction5(validate)) {
|
|
4189
5327
|
const permissions = this.getGlobalPermissions();
|
|
4190
5328
|
return validate.call(this.req, allowedData, permissions, context);
|
|
4191
|
-
} else if (isBoolean5(validate) ||
|
|
5329
|
+
} else if (isBoolean5(validate) || isArray11(validate)) {
|
|
4192
5330
|
return validate;
|
|
4193
5331
|
} else {
|
|
4194
5332
|
return true;
|
|
@@ -4332,18 +5470,55 @@ var Core = class {
|
|
|
4332
5470
|
options
|
|
4333
5471
|
);
|
|
4334
5472
|
break;
|
|
5473
|
+
case "COPY":
|
|
5474
|
+
docObject = copyPaths(
|
|
5475
|
+
docObject,
|
|
5476
|
+
args,
|
|
5477
|
+
options
|
|
5478
|
+
);
|
|
5479
|
+
break;
|
|
5480
|
+
case "MOVE":
|
|
5481
|
+
docObject = movePaths(
|
|
5482
|
+
docObject,
|
|
5483
|
+
args,
|
|
5484
|
+
options
|
|
5485
|
+
);
|
|
5486
|
+
break;
|
|
5487
|
+
case "SLICE":
|
|
5488
|
+
docObject = sliceArrays(
|
|
5489
|
+
docObject,
|
|
5490
|
+
args,
|
|
5491
|
+
options
|
|
5492
|
+
);
|
|
5493
|
+
break;
|
|
5494
|
+
case "COUNT":
|
|
5495
|
+
docObject = countPaths(
|
|
5496
|
+
docObject,
|
|
5497
|
+
args,
|
|
5498
|
+
options
|
|
5499
|
+
);
|
|
5500
|
+
break;
|
|
5501
|
+
case "MASK":
|
|
5502
|
+
docObject = maskPaths(
|
|
5503
|
+
docObject,
|
|
5504
|
+
args,
|
|
5505
|
+
options
|
|
5506
|
+
);
|
|
5507
|
+
break;
|
|
4335
5508
|
}
|
|
4336
5509
|
});
|
|
4337
5510
|
return docObject;
|
|
4338
5511
|
}
|
|
4339
5512
|
getPermissions() {
|
|
5513
|
+
if (this.cachedPermissions) return this.cachedPermissions;
|
|
4340
5514
|
return getResolvedRequestPermissions(this.req);
|
|
4341
5515
|
}
|
|
4342
5516
|
async setPermissions() {
|
|
4343
5517
|
await setResolvedRequestPermissions(this.req);
|
|
5518
|
+
this.cachedPermissions = getResolvedRequestPermissions(this.req);
|
|
4344
5519
|
}
|
|
4345
5520
|
async canActivate(routeGuard) {
|
|
4346
|
-
return
|
|
5521
|
+
return evaluateRouteGuard(this.req, this.getGlobalPermissions(), routeGuard);
|
|
4347
5522
|
}
|
|
4348
5523
|
async isAllowed(modelName, access) {
|
|
4349
5524
|
if (access.startsWith("subs")) {
|
|
@@ -4379,6 +5554,7 @@ var Core = class {
|
|
|
4379
5554
|
return this.getPublicService(modelName);
|
|
4380
5555
|
}
|
|
4381
5556
|
getGlobalPermissions() {
|
|
5557
|
+
if (this.cachedPermissions) return this.cachedPermissions;
|
|
4382
5558
|
return getGlobalPermissions(this.req);
|
|
4383
5559
|
}
|
|
4384
5560
|
};
|
|
@@ -4388,6 +5564,7 @@ var createSetCore = (runtime = defaultRuntime) => {
|
|
|
4388
5564
|
await initializeAclRequest({
|
|
4389
5565
|
req,
|
|
4390
5566
|
flag: MIDDLEWARE,
|
|
5567
|
+
runtime,
|
|
4391
5568
|
createCore: (request) => new Core(request),
|
|
4392
5569
|
assignCore: (core) => {
|
|
4393
5570
|
req.macl = core;
|
|
@@ -4405,19 +5582,19 @@ function guard(condition) {
|
|
|
4405
5582
|
const permissions = req[PERMISSIONS];
|
|
4406
5583
|
let cond = condition;
|
|
4407
5584
|
let phas = (key) => permissions.has(key);
|
|
4408
|
-
if (
|
|
5585
|
+
if (isPlainObject16(condition)) {
|
|
4409
5586
|
const { modelName, id, condition: _cond } = condition;
|
|
4410
5587
|
const svc = req.macl.getPublicService(modelName);
|
|
4411
5588
|
const select = getModelOption(modelName, `alwaysSelectFields.read`, void 0);
|
|
4412
5589
|
let _id = isString7(id) ? id : null;
|
|
4413
|
-
if (
|
|
5590
|
+
if (isPlainObject16(id)) {
|
|
4414
5591
|
const { type = "param", key } = id;
|
|
4415
5592
|
if (type === "param") {
|
|
4416
5593
|
const paramValue = req.params[key];
|
|
4417
|
-
_id =
|
|
5594
|
+
_id = isArray12(paramValue) ? paramValue[0] ?? null : paramValue ?? null;
|
|
4418
5595
|
} else if (type === "query") {
|
|
4419
5596
|
const _qval = req.query[key];
|
|
4420
|
-
if (
|
|
5597
|
+
if (isArray12(_qval)) _id = _qval.length > 0 ? _qval[0] : null;
|
|
4421
5598
|
else _id = _qval;
|
|
4422
5599
|
} else {
|
|
4423
5600
|
_id = null;
|
|
@@ -4437,7 +5614,7 @@ function guard(condition) {
|
|
|
4437
5614
|
const { stringHandler, arrayHandler } = createValidator(phas);
|
|
4438
5615
|
if (isString7(cond)) {
|
|
4439
5616
|
if (stringHandler(cond)) return next();
|
|
4440
|
-
} else if (
|
|
5617
|
+
} else if (isArray12(cond)) {
|
|
4441
5618
|
if (arrayHandler(cond)) return next();
|
|
4442
5619
|
} else if (isFunction6(cond)) {
|
|
4443
5620
|
const result = await cond.call(req, permissions);
|
|
@@ -4452,7 +5629,7 @@ import express2 from "express";
|
|
|
4452
5629
|
|
|
4453
5630
|
// src/routers/model-router.ts
|
|
4454
5631
|
import JsonRouter5 from "@web-ts-toolkit/express-json-router";
|
|
4455
|
-
import { forEach as forEach8, isPlainObject as
|
|
5632
|
+
import { forEach as forEach8, isPlainObject as isPlainObject18, isString as isString8, isUndefined as isUndefined2, normalizeUrlPath as normalizeUrlPath2, padEnd } from "@web-ts-toolkit/utils";
|
|
4456
5633
|
|
|
4457
5634
|
// src/routers/router-mutation.ts
|
|
4458
5635
|
var MODEL_BUILD_TIME_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -4581,6 +5758,7 @@ import JsonRouter4 from "@web-ts-toolkit/express-json-router";
|
|
|
4581
5758
|
|
|
4582
5759
|
// src/validation/common.ts
|
|
4583
5760
|
import { z as z2 } from "zod";
|
|
5761
|
+
import { isPlainObject as isPlainObject17 } from "@web-ts-toolkit/utils";
|
|
4584
5762
|
var stringOrStringArray = z2.union([z2.string(), z2.array(z2.string())]);
|
|
4585
5763
|
var safeIntegerString = (min, message) => z2.string().regex(/^\d+$/, message).refine((value) => Number.isSafeInteger(Number(value)), "Expected a safe integer").refine((value) => Number(value) >= min, message);
|
|
4586
5764
|
var queryBooleanString = z2.enum(["true", "false"]);
|
|
@@ -4641,7 +5819,8 @@ var subPopulateSchema = z2.union([
|
|
|
4641
5819
|
select: projectionSchema.optional()
|
|
4642
5820
|
}).passthrough()
|
|
4643
5821
|
]);
|
|
4644
|
-
var
|
|
5822
|
+
var legacyIncludeItemSchema = z2.object({
|
|
5823
|
+
mode: z2.enum(["legacy"]).optional(),
|
|
4645
5824
|
model: z2.string().min(1),
|
|
4646
5825
|
op: z2.enum(["list", "read", "count"]),
|
|
4647
5826
|
path: z2.string().min(1),
|
|
@@ -4650,8 +5829,139 @@ var includeItemSchema = z2.object({
|
|
|
4650
5829
|
foreignField: z2.string().min(1),
|
|
4651
5830
|
args: z2.unknown().optional().describe("Forwarded to list/read includes. Count includes ignore pagination fields so counts remain exact."),
|
|
4652
5831
|
options: z2.unknown().optional()
|
|
4653
|
-
}).passthrough()
|
|
4654
|
-
|
|
5832
|
+
}).passthrough().superRefine((entry, ctx) => {
|
|
5833
|
+
if (entry.filter !== void 0 && containsParentMarkerShape(entry.filter)) {
|
|
5834
|
+
ctx.addIssue({
|
|
5835
|
+
code: z2.ZodIssueCode.custom,
|
|
5836
|
+
message: 'Legacy includes must not contain $parent markers; use a correlated include (mode: "correlated")',
|
|
5837
|
+
path: ["filter"]
|
|
5838
|
+
});
|
|
5839
|
+
}
|
|
5840
|
+
});
|
|
5841
|
+
var parentRefSchema = z2.object({ $parent: z2.string().min(1) }).strict().superRefine((value, ctx) => {
|
|
5842
|
+
if (!isValidParentRefPath(value.$parent)) {
|
|
5843
|
+
ctx.addIssue({
|
|
5844
|
+
code: z2.ZodIssueCode.custom,
|
|
5845
|
+
message: `Invalid $parent reference path: ${value.$parent}`,
|
|
5846
|
+
path: ["$parent"]
|
|
5847
|
+
});
|
|
5848
|
+
}
|
|
5849
|
+
});
|
|
5850
|
+
var correlatedIdSchema = z2.union([z2.string().min(1), parentRefSchema]);
|
|
5851
|
+
var correlatedFilterSchema = z2.record(z2.string(), z2.unknown()).superRefine((filter2, ctx) => {
|
|
5852
|
+
for (const issue of validateCorrelatedFilterTemplate(filter2)) {
|
|
5853
|
+
ctx.addIssue({
|
|
5854
|
+
code: z2.ZodIssueCode.custom,
|
|
5855
|
+
message: issue.message,
|
|
5856
|
+
path: issue.path.map(String)
|
|
5857
|
+
});
|
|
5858
|
+
}
|
|
5859
|
+
});
|
|
5860
|
+
var correlatedEmptyOptionsSchema = z2.unknown().refine(
|
|
5861
|
+
(value) => value === void 0 || isPlainObject17(value) && Object.keys(value).length === 0,
|
|
5862
|
+
"Correlated include options must be absent or empty"
|
|
5863
|
+
).optional();
|
|
5864
|
+
var correlatedPathSchema = z2.string().min(1).superRefine((path, ctx) => {
|
|
5865
|
+
if (!isValidCorrelatedOutputPath(path)) {
|
|
5866
|
+
ctx.addIssue({
|
|
5867
|
+
code: z2.ZodIssueCode.custom,
|
|
5868
|
+
message: `Invalid correlated include output path: ${path}`,
|
|
5869
|
+
path: []
|
|
5870
|
+
});
|
|
5871
|
+
}
|
|
5872
|
+
});
|
|
5873
|
+
var checkArgsMarkers = (select, sort, ctx) => {
|
|
5874
|
+
const selectIssue = findArgsMarkerIssue(select, "args.select");
|
|
5875
|
+
if (selectIssue) {
|
|
5876
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, message: selectIssue.message, path: ["select"] });
|
|
5877
|
+
}
|
|
5878
|
+
const sortIssue = findArgsMarkerIssue(sort, "args.sort");
|
|
5879
|
+
if (sortIssue) {
|
|
5880
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, message: sortIssue.message, path: ["sort"] });
|
|
5881
|
+
}
|
|
5882
|
+
};
|
|
5883
|
+
var correlatedReadArgsSchema = z2.object({
|
|
5884
|
+
select: projectionSchema.optional(),
|
|
5885
|
+
sort: sortSchema.optional(),
|
|
5886
|
+
include: z2.lazy(() => correlatedNestedIncludeSchema).optional()
|
|
5887
|
+
}).strict().superRefine((args, ctx) => checkArgsMarkers(args.select, args.sort, ctx));
|
|
5888
|
+
var correlatedListArgsSchema = z2.object({
|
|
5889
|
+
select: projectionSchema.optional(),
|
|
5890
|
+
sort: sortSchema.optional(),
|
|
5891
|
+
skip: z2.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
5892
|
+
limit: z2.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
5893
|
+
page: z2.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
5894
|
+
pageSize: z2.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
5895
|
+
include: z2.lazy(() => correlatedNestedIncludeSchema).optional()
|
|
5896
|
+
}).strict().superRefine((args, ctx) => checkArgsMarkers(args.select, args.sort, ctx));
|
|
5897
|
+
var correlatedCountArgsSchema = z2.unknown().refine(
|
|
5898
|
+
(value) => value === void 0 || isPlainObject17(value) && Object.keys(value).length === 0,
|
|
5899
|
+
"Count includes accept no args"
|
|
5900
|
+
).optional();
|
|
5901
|
+
var correlatedReadByIdSchema = z2.object({
|
|
5902
|
+
mode: z2.literal("correlated"),
|
|
5903
|
+
model: z2.string().min(1),
|
|
5904
|
+
op: z2.literal("read"),
|
|
5905
|
+
path: correlatedPathSchema,
|
|
5906
|
+
id: correlatedIdSchema,
|
|
5907
|
+
args: correlatedReadArgsSchema.optional(),
|
|
5908
|
+
options: correlatedEmptyOptionsSchema
|
|
5909
|
+
}).strict();
|
|
5910
|
+
var correlatedReadByFilterSchema = z2.object({
|
|
5911
|
+
mode: z2.literal("correlated"),
|
|
5912
|
+
model: z2.string().min(1),
|
|
5913
|
+
op: z2.literal("read"),
|
|
5914
|
+
path: correlatedPathSchema,
|
|
5915
|
+
filter: correlatedFilterSchema,
|
|
5916
|
+
args: correlatedReadArgsSchema.optional(),
|
|
5917
|
+
options: correlatedEmptyOptionsSchema
|
|
5918
|
+
}).strict();
|
|
5919
|
+
var correlatedListSchema = z2.object({
|
|
5920
|
+
mode: z2.literal("correlated"),
|
|
5921
|
+
model: z2.string().min(1),
|
|
5922
|
+
op: z2.literal("list"),
|
|
5923
|
+
path: correlatedPathSchema,
|
|
5924
|
+
filter: correlatedFilterSchema,
|
|
5925
|
+
args: correlatedListArgsSchema.optional(),
|
|
5926
|
+
options: correlatedEmptyOptionsSchema
|
|
5927
|
+
}).strict();
|
|
5928
|
+
var correlatedCountSchema = z2.object({
|
|
5929
|
+
mode: z2.literal("correlated"),
|
|
5930
|
+
model: z2.string().min(1),
|
|
5931
|
+
op: z2.literal("count"),
|
|
5932
|
+
path: correlatedPathSchema,
|
|
5933
|
+
filter: correlatedFilterSchema,
|
|
5934
|
+
args: correlatedCountArgsSchema,
|
|
5935
|
+
options: correlatedEmptyOptionsSchema
|
|
5936
|
+
}).strict();
|
|
5937
|
+
var includeItemSchema = z2.union([
|
|
5938
|
+
legacyIncludeItemSchema,
|
|
5939
|
+
correlatedReadByIdSchema,
|
|
5940
|
+
correlatedReadByFilterSchema,
|
|
5941
|
+
correlatedListSchema,
|
|
5942
|
+
correlatedCountSchema
|
|
5943
|
+
]);
|
|
5944
|
+
var correlatedNestedIncludeSchema = z2.union([includeItemSchema, z2.array(includeItemSchema)]);
|
|
5945
|
+
var rejectDuplicateCorrelatedPaths = (items, ctx) => {
|
|
5946
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5947
|
+
items.forEach((item, index) => {
|
|
5948
|
+
if (isPlainObject17(item) && item.mode === "correlated" && typeof item.path === "string") {
|
|
5949
|
+
if (seen.has(item.path)) {
|
|
5950
|
+
ctx.addIssue({
|
|
5951
|
+
code: z2.ZodIssueCode.custom,
|
|
5952
|
+
message: `Duplicate correlated include output path: ${item.path}`,
|
|
5953
|
+
path: [index, "path"]
|
|
5954
|
+
});
|
|
5955
|
+
} else {
|
|
5956
|
+
seen.add(item.path);
|
|
5957
|
+
}
|
|
5958
|
+
}
|
|
5959
|
+
});
|
|
5960
|
+
};
|
|
5961
|
+
var includeSchema = z2.union([
|
|
5962
|
+
includeItemSchema,
|
|
5963
|
+
z2.array(includeItemSchema).superRefine(rejectDuplicateCorrelatedPaths)
|
|
5964
|
+
]);
|
|
4655
5965
|
var fieldsSchema = z2.array(z2.string().min(1));
|
|
4656
5966
|
var taskSchema = z2.object({
|
|
4657
5967
|
type: z2.string().min(1),
|
|
@@ -4768,8 +6078,9 @@ function defineRequestSchema(validator, options = {}) {
|
|
|
4768
6078
|
};
|
|
4769
6079
|
}
|
|
4770
6080
|
function fromZod(schema) {
|
|
4771
|
-
return (value) => {
|
|
4772
|
-
const
|
|
6081
|
+
return async (value) => {
|
|
6082
|
+
const candidate = schema;
|
|
6083
|
+
const result = typeof candidate.safeParseAsync === "function" ? await candidate.safeParseAsync.call(schema, value) : schema.safeParse(value);
|
|
4773
6084
|
if (result.success) {
|
|
4774
6085
|
return {
|
|
4775
6086
|
success: true,
|
|
@@ -4806,6 +6117,9 @@ function fromYup(schema) {
|
|
|
4806
6117
|
data
|
|
4807
6118
|
};
|
|
4808
6119
|
} catch (error2) {
|
|
6120
|
+
if (!isYupValidationError(error2)) {
|
|
6121
|
+
throw error2;
|
|
6122
|
+
}
|
|
4809
6123
|
return {
|
|
4810
6124
|
success: false,
|
|
4811
6125
|
issues: normalizeYupIssues(error2)
|
|
@@ -4830,8 +6144,32 @@ function fromJoi(schema) {
|
|
|
4830
6144
|
}
|
|
4831
6145
|
function fromAjv(validator) {
|
|
4832
6146
|
return async (value) => {
|
|
4833
|
-
const
|
|
4834
|
-
if (
|
|
6147
|
+
const outcome = validator(value);
|
|
6148
|
+
if (isThenable(outcome)) {
|
|
6149
|
+
try {
|
|
6150
|
+
const data = await outcome;
|
|
6151
|
+
if (data === false) {
|
|
6152
|
+
return {
|
|
6153
|
+
success: false,
|
|
6154
|
+
issues: normalizeAjvIssues(validator.errors ?? [])
|
|
6155
|
+
};
|
|
6156
|
+
}
|
|
6157
|
+
return {
|
|
6158
|
+
success: true,
|
|
6159
|
+
data: data === true ? value : data
|
|
6160
|
+
};
|
|
6161
|
+
} catch (error2) {
|
|
6162
|
+
if (!isAjvValidationError(error2)) {
|
|
6163
|
+
throw error2;
|
|
6164
|
+
}
|
|
6165
|
+
return {
|
|
6166
|
+
success: false,
|
|
6167
|
+
issues: normalizeAjvIssues(error2.errors ?? [])
|
|
6168
|
+
};
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
const errorsSnapshot = validator.errors ? [...validator.errors] : [];
|
|
6172
|
+
if (outcome) {
|
|
4835
6173
|
return {
|
|
4836
6174
|
success: true,
|
|
4837
6175
|
data: value
|
|
@@ -4839,7 +6177,7 @@ function fromAjv(validator) {
|
|
|
4839
6177
|
}
|
|
4840
6178
|
return {
|
|
4841
6179
|
success: false,
|
|
4842
|
-
issues: normalizeAjvIssues(
|
|
6180
|
+
issues: normalizeAjvIssues(errorsSnapshot)
|
|
4843
6181
|
};
|
|
4844
6182
|
};
|
|
4845
6183
|
}
|
|
@@ -4860,6 +6198,25 @@ function fromValibot(schema, safeParse) {
|
|
|
4860
6198
|
}
|
|
4861
6199
|
function fromArkType(type) {
|
|
4862
6200
|
return async (value) => {
|
|
6201
|
+
if (isStandardSchema(type)) {
|
|
6202
|
+
const result2 = await type["~standard"].validate(value);
|
|
6203
|
+
if (isArkTypeErrors(result2)) {
|
|
6204
|
+
return {
|
|
6205
|
+
success: false,
|
|
6206
|
+
issues: normalizeArkTypeIssues(result2)
|
|
6207
|
+
};
|
|
6208
|
+
}
|
|
6209
|
+
if (isStandardSchemaFailure(result2)) {
|
|
6210
|
+
return {
|
|
6211
|
+
success: false,
|
|
6212
|
+
issues: normalizeArkTypeStandardIssues(result2.issues)
|
|
6213
|
+
};
|
|
6214
|
+
}
|
|
6215
|
+
return {
|
|
6216
|
+
success: true,
|
|
6217
|
+
data: result2.value
|
|
6218
|
+
};
|
|
6219
|
+
}
|
|
4863
6220
|
const result = await type(value);
|
|
4864
6221
|
if (!isArkTypeErrors(result)) {
|
|
4865
6222
|
return {
|
|
@@ -4912,6 +6269,9 @@ function fromVine(validator) {
|
|
|
4912
6269
|
data: output
|
|
4913
6270
|
};
|
|
4914
6271
|
} catch (error2) {
|
|
6272
|
+
if (!isVineValidationError(error2)) {
|
|
6273
|
+
throw error2;
|
|
6274
|
+
}
|
|
4915
6275
|
return {
|
|
4916
6276
|
success: false,
|
|
4917
6277
|
issues: normalizeVineError(error2)
|
|
@@ -4972,6 +6332,13 @@ function normalizeArkTypeIssues(issues) {
|
|
|
4972
6332
|
}));
|
|
4973
6333
|
return normalized.length ? normalized : [{ message: issues.summary ?? "Validation failed" }];
|
|
4974
6334
|
}
|
|
6335
|
+
function normalizeArkTypeStandardIssues(issues) {
|
|
6336
|
+
const normalized = issues.map((issue) => ({
|
|
6337
|
+
message: issue.message ?? issue.problem ?? "Validation failed",
|
|
6338
|
+
path: issue.path?.flatMap((segment) => normalizePathSegment(segment))
|
|
6339
|
+
}));
|
|
6340
|
+
return normalized.length ? normalized : [{ message: "Validation failed" }];
|
|
6341
|
+
}
|
|
4975
6342
|
function normalizeIoTsIssues(issues) {
|
|
4976
6343
|
return issues.map((issue) => ({
|
|
4977
6344
|
message: issue.message ?? "Validation failed",
|
|
@@ -5006,25 +6373,64 @@ function normalizeVineField(message) {
|
|
|
5006
6373
|
}
|
|
5007
6374
|
return path.length ? path : void 0;
|
|
5008
6375
|
}
|
|
6376
|
+
function decodeAjvSegment(segment) {
|
|
6377
|
+
return segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
6378
|
+
}
|
|
6379
|
+
function encodePointerSegment(segment) {
|
|
6380
|
+
return encodeURIComponent(segment.replace(/~/g, "~0").replace(/\//g, "~1"));
|
|
6381
|
+
}
|
|
5009
6382
|
function parseAjvPath(issue) {
|
|
5010
|
-
const
|
|
5011
|
-
|
|
5012
|
-
|
|
6383
|
+
const raw = issue.instancePath;
|
|
6384
|
+
const base = raw == null || raw === "" ? [] : (raw.startsWith("/") ? raw.slice(1) : raw).split("/").map(decodeAjvSegment);
|
|
6385
|
+
const missing = issue.params?.missingProperty;
|
|
6386
|
+
if (typeof missing === "string") {
|
|
6387
|
+
return [...base, missing];
|
|
5013
6388
|
}
|
|
5014
|
-
return
|
|
6389
|
+
return base;
|
|
5015
6390
|
}
|
|
5016
6391
|
function parsePathString(path) {
|
|
5017
6392
|
if (!path) return void 0;
|
|
5018
6393
|
return path.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean).map((segment) => /^\d+$/.test(segment) ? Number(segment) : segment);
|
|
5019
6394
|
}
|
|
5020
6395
|
function isYupValidationError(error2) {
|
|
5021
|
-
|
|
6396
|
+
if (typeof error2 !== "object" || error2 === null || !("message" in error2)) {
|
|
6397
|
+
return false;
|
|
6398
|
+
}
|
|
6399
|
+
const candidate = error2;
|
|
6400
|
+
return candidate.name === "ValidationError" && typeof candidate.message === "string" && Array.isArray(candidate.errors) && Array.isArray(candidate.inner);
|
|
5022
6401
|
}
|
|
5023
6402
|
function isArkTypeErrors(value) {
|
|
5024
|
-
|
|
6403
|
+
if (typeof value !== "object" || value === null) {
|
|
6404
|
+
return false;
|
|
6405
|
+
}
|
|
6406
|
+
const record = value;
|
|
6407
|
+
return record.arkKind === "errors" || record[" arkKind"] === "errors";
|
|
6408
|
+
}
|
|
6409
|
+
function isThenable(value) {
|
|
6410
|
+
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
6411
|
+
}
|
|
6412
|
+
function isAjvValidationError(error2) {
|
|
6413
|
+
if (typeof error2 !== "object" || error2 === null || !("errors" in error2)) {
|
|
6414
|
+
return false;
|
|
6415
|
+
}
|
|
6416
|
+
const errors = error2.errors;
|
|
6417
|
+
return errors == null || Array.isArray(errors);
|
|
5025
6418
|
}
|
|
5026
6419
|
function isVineValidationError(error2) {
|
|
5027
|
-
|
|
6420
|
+
if (typeof error2 !== "object" || error2 === null || !("messages" in error2)) {
|
|
6421
|
+
return false;
|
|
6422
|
+
}
|
|
6423
|
+
const candidate = error2;
|
|
6424
|
+
if (!Array.isArray(candidate.messages) || candidate.messages.length === 0) {
|
|
6425
|
+
return false;
|
|
6426
|
+
}
|
|
6427
|
+
const hasDiscriminator = candidate.code === "E_VALIDATION_ERROR" || candidate.name === "ValidationError";
|
|
6428
|
+
if (!hasDiscriminator) {
|
|
6429
|
+
return false;
|
|
6430
|
+
}
|
|
6431
|
+
return candidate.messages.every(
|
|
6432
|
+
(message) => typeof message === "object" && message !== null && typeof message.message === "string" && typeof message.field === "string"
|
|
6433
|
+
);
|
|
5028
6434
|
}
|
|
5029
6435
|
function formatIssue(issue, key, location = "pointer") {
|
|
5030
6436
|
const path = (issue.path ?? []).map(String);
|
|
@@ -5041,7 +6447,7 @@ function formatIssue(issue, key, location = "pointer") {
|
|
|
5041
6447
|
};
|
|
5042
6448
|
}
|
|
5043
6449
|
function buildPointer(path) {
|
|
5044
|
-
return path.length === 0 ? "#" : `#/${path.join("/")}`;
|
|
6450
|
+
return path.length === 0 ? "#" : `#/${path.map(encodePointerSegment).join("/")}`;
|
|
5045
6451
|
}
|
|
5046
6452
|
|
|
5047
6453
|
// src/validation/model-router.ts
|
|
@@ -5092,7 +6498,7 @@ var listBodySchema = z3.object({
|
|
|
5092
6498
|
}).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query"]));
|
|
5093
6499
|
var countBodySchema = z3.object({
|
|
5094
6500
|
filter: objectOrArraySchema.optional()
|
|
5095
|
-
}).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query", "access", "options"]));
|
|
6501
|
+
}).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query", "access", "options", "include"]));
|
|
5096
6502
|
var readFilterBodySchema = z3.object({
|
|
5097
6503
|
filter: objectOrArraySchema.optional(),
|
|
5098
6504
|
select: projectionSchema.optional(),
|
|
@@ -5542,24 +6948,26 @@ function setModelCollectionRoutes(context) {
|
|
|
5542
6948
|
router.post(`/${options.mutationRouteSegment}`, async (req) => {
|
|
5543
6949
|
await context.assertAllowed(req, "create");
|
|
5544
6950
|
const { include_permissions } = parseQuery(requestSchemas.createQuery, req.query);
|
|
5545
|
-
const
|
|
6951
|
+
const intermediateBody = await parseNestedBodyWithSchema(
|
|
5546
6952
|
advancedCreateBodySchema,
|
|
5547
6953
|
req.body,
|
|
5548
6954
|
"data",
|
|
5549
6955
|
context.getRequestSchema("requestSchemas.advancedCreate.data")
|
|
5550
6956
|
);
|
|
5551
|
-
const { data, select, populate, tasks } =
|
|
5552
|
-
const
|
|
5553
|
-
await parseBodyWithSchema(
|
|
6957
|
+
const { data: nestedData, select: nestedSelect, populate: nestedPopulate, tasks: nestedTasks } = intermediateBody;
|
|
6958
|
+
const nestedOptions = intermediateBody.options ?? {};
|
|
6959
|
+
const finalBody = await parseBodyWithSchema(
|
|
5554
6960
|
advancedCreateBodySchema,
|
|
5555
|
-
{ data, select, populate, tasks, options:
|
|
6961
|
+
{ data: nestedData, select: nestedSelect, populate: nestedPopulate, tasks: nestedTasks, options: nestedOptions },
|
|
5556
6962
|
context.getRequestSchema("requestSchemas.advancedCreate.default") ?? context.getRequestSchema("requestSchemas.advancedCreate")
|
|
5557
6963
|
);
|
|
5558
|
-
const {
|
|
6964
|
+
const { data: finalData, select: finalSelect, populate: finalPopulate, tasks: finalTasks } = finalBody;
|
|
6965
|
+
const finalOptions = finalBody.options ?? {};
|
|
6966
|
+
const { includePermissions, populateAccess } = finalOptions;
|
|
5559
6967
|
const svc = context.getPublicService(req);
|
|
5560
6968
|
const result = await svc._create(
|
|
5561
|
-
|
|
5562
|
-
{ select, populate, tasks },
|
|
6969
|
+
finalData,
|
|
6970
|
+
{ select: finalSelect, populate: finalPopulate, tasks: finalTasks },
|
|
5563
6971
|
{
|
|
5564
6972
|
includePermissions: includePermissions ?? parseBooleanString(include_permissions),
|
|
5565
6973
|
populateAccess
|
|
@@ -5758,25 +7166,33 @@ function setModelDocumentRoutes(context) {
|
|
|
5758
7166
|
await context.assertAllowed(req, "update");
|
|
5759
7167
|
const id = parsePathParam(req.params[options.idParam], options.idParam);
|
|
5760
7168
|
const { returning_all, include_permissions } = parseQuery(requestSchemas.updateQuery, req.query);
|
|
5761
|
-
const
|
|
7169
|
+
const intermediateBody = await parseNestedBodyWithSchema(
|
|
5762
7170
|
advancedUpdateBodySchema,
|
|
5763
7171
|
req.body,
|
|
5764
7172
|
"data",
|
|
5765
7173
|
context.getRequestSchema("requestSchemas.advancedUpdate.data")
|
|
5766
7174
|
);
|
|
5767
|
-
const { data, select, populate, tasks } =
|
|
5768
|
-
const
|
|
5769
|
-
await parseBodyWithSchema(
|
|
7175
|
+
const { data: nestedData, select: nestedSelect, populate: nestedPopulate, tasks: nestedTasks } = intermediateBody;
|
|
7176
|
+
const nestedOptions = intermediateBody.options ?? {};
|
|
7177
|
+
const finalBody = await parseBodyWithSchema(
|
|
5770
7178
|
advancedUpdateBodySchema,
|
|
5771
|
-
{
|
|
7179
|
+
{
|
|
7180
|
+
data: nestedData,
|
|
7181
|
+
select: nestedSelect,
|
|
7182
|
+
populate: nestedPopulate,
|
|
7183
|
+
tasks: nestedTasks,
|
|
7184
|
+
options: nestedOptions
|
|
7185
|
+
},
|
|
5772
7186
|
context.getRequestSchema("requestSchemas.advancedUpdate.default") ?? context.getRequestSchema("requestSchemas.advancedUpdate")
|
|
5773
7187
|
);
|
|
5774
|
-
const {
|
|
7188
|
+
const { data: finalData, select: finalSelect, populate: finalPopulate, tasks: finalTasks } = finalBody;
|
|
7189
|
+
const finalOptions = finalBody.options ?? {};
|
|
7190
|
+
const { returningAll, includePermissions, populateAccess } = finalOptions;
|
|
5775
7191
|
const svc = context.getPublicService(req);
|
|
5776
7192
|
const result = await svc._update(
|
|
5777
7193
|
id,
|
|
5778
|
-
|
|
5779
|
-
{ select, populate, tasks },
|
|
7194
|
+
finalData,
|
|
7195
|
+
{ select: finalSelect, populate: finalPopulate, tasks: finalTasks },
|
|
5780
7196
|
{
|
|
5781
7197
|
returningAll: returningAll ?? parseBooleanString(returning_all),
|
|
5782
7198
|
includePermissions: includePermissions ?? parseBooleanString(include_permissions),
|
|
@@ -5826,23 +7242,31 @@ function setModelDocumentRoutes(context) {
|
|
|
5826
7242
|
await context.assertAllowed(req, "upsert");
|
|
5827
7243
|
const svc = context.getPublicService(req);
|
|
5828
7244
|
const { returning_all, include_permissions } = parseQuery(requestSchemas.upsertQuery, req.query);
|
|
5829
|
-
const
|
|
7245
|
+
const intermediateBody = await parseNestedBodyWithSchema(
|
|
5830
7246
|
advancedUpsertBodySchema,
|
|
5831
7247
|
req.body,
|
|
5832
7248
|
"data",
|
|
5833
7249
|
context.getRequestSchema("requestSchemas.advancedUpsert.data")
|
|
5834
7250
|
);
|
|
5835
|
-
const { data, select, populate, tasks } =
|
|
5836
|
-
const
|
|
5837
|
-
await parseBodyWithSchema(
|
|
7251
|
+
const { data: nestedData, select: nestedSelect, populate: nestedPopulate, tasks: nestedTasks } = intermediateBody;
|
|
7252
|
+
const nestedOptions = intermediateBody.options ?? {};
|
|
7253
|
+
const finalBody = await parseBodyWithSchema(
|
|
5838
7254
|
advancedUpsertBodySchema,
|
|
5839
|
-
{
|
|
7255
|
+
{
|
|
7256
|
+
data: nestedData,
|
|
7257
|
+
select: nestedSelect,
|
|
7258
|
+
populate: nestedPopulate,
|
|
7259
|
+
tasks: nestedTasks,
|
|
7260
|
+
options: nestedOptions
|
|
7261
|
+
},
|
|
5840
7262
|
context.getRequestSchema("requestSchemas.advancedUpsert.default") ?? context.getRequestSchema("requestSchemas.advancedUpsert")
|
|
5841
7263
|
);
|
|
5842
|
-
const {
|
|
7264
|
+
const { data: finalData, select: finalSelect, populate: finalPopulate, tasks: finalTasks } = finalBody;
|
|
7265
|
+
const finalOptions = finalBody.options ?? {};
|
|
7266
|
+
const { returningAll, includePermissions, populateAccess } = finalOptions;
|
|
5843
7267
|
const result = await svc._upsert(
|
|
5844
|
-
|
|
5845
|
-
{ select, populate, tasks },
|
|
7268
|
+
finalData,
|
|
7269
|
+
{ select: finalSelect, populate: finalPopulate, tasks: finalTasks },
|
|
5846
7270
|
{
|
|
5847
7271
|
returningAll: returningAll ?? parseBooleanString(returning_all),
|
|
5848
7272
|
includePermissions: includePermissions ?? parseBooleanString(include_permissions),
|
|
@@ -5912,7 +7336,7 @@ function setModelDocumentRoutes(context) {
|
|
|
5912
7336
|
|
|
5913
7337
|
// src/routers/model-router-subdocument-routes.ts
|
|
5914
7338
|
function setModelSubDocumentRoutes(context) {
|
|
5915
|
-
const subs = getModelSub(context.modelName);
|
|
7339
|
+
const subs = context.runtime.getModelSub(context.modelName);
|
|
5916
7340
|
for (let x = 0; x < subs.length; x++) {
|
|
5917
7341
|
const sub = subs[x];
|
|
5918
7342
|
context.router.get(`/:${context.options.idParam}/${sub}`, async (req) => {
|
|
@@ -6137,7 +7561,9 @@ var ModelRouter = class _ModelRouter {
|
|
|
6137
7561
|
"baseFilter"
|
|
6138
7562
|
);
|
|
6139
7563
|
/**
|
|
6140
|
-
*
|
|
7564
|
+
* Trusted filter replacements applied before base-filter composition.
|
|
7565
|
+
* They may replace ordinary filters, but cannot replace an existing `false` denial.
|
|
7566
|
+
* Returning `false` denies the transaction.
|
|
6141
7567
|
* @operation `list`, `read`, `update`, `delete`
|
|
6142
7568
|
*/
|
|
6143
7569
|
this.overrideFilter = setOption.bind(this, "overrideFilter");
|
|
@@ -6297,6 +7723,7 @@ var ModelRouter = class _ModelRouter {
|
|
|
6297
7723
|
modelName: this.modelName,
|
|
6298
7724
|
router: this.router,
|
|
6299
7725
|
options: this.options,
|
|
7726
|
+
runtime: this.runtime,
|
|
6300
7727
|
getRequestSchema: this.getRequestSchema.bind(this),
|
|
6301
7728
|
getPublicService: this.getPublicService.bind(this),
|
|
6302
7729
|
assertAllowed: this.assertAllowed.bind(this),
|
|
@@ -6311,6 +7738,7 @@ var ModelRouter = class _ModelRouter {
|
|
|
6311
7738
|
modelName: this.modelName,
|
|
6312
7739
|
router: this.router,
|
|
6313
7740
|
options: this.options,
|
|
7741
|
+
runtime: this.runtime,
|
|
6314
7742
|
getRequestSchema: this.getRequestSchema.bind(this),
|
|
6315
7743
|
getPublicService: this.getPublicService.bind(this),
|
|
6316
7744
|
assertAllowed: this.assertAllowed.bind(this),
|
|
@@ -6325,6 +7753,7 @@ var ModelRouter = class _ModelRouter {
|
|
|
6325
7753
|
modelName: this.modelName,
|
|
6326
7754
|
router: this.router,
|
|
6327
7755
|
options: this.options,
|
|
7756
|
+
runtime: this.runtime,
|
|
6328
7757
|
getRequestSchema: this.getRequestSchema.bind(this),
|
|
6329
7758
|
getPublicService: this.getPublicService.bind(this),
|
|
6330
7759
|
assertAllowed: this.assertAllowed.bind(this),
|
|
@@ -6347,7 +7776,7 @@ var ModelRouter = class _ModelRouter {
|
|
|
6347
7776
|
value
|
|
6348
7777
|
);
|
|
6349
7778
|
}
|
|
6350
|
-
if (arguments.length === 1 &&
|
|
7779
|
+
if (arguments.length === 1 && isPlainObject18(keyOrOptions)) {
|
|
6351
7780
|
assertMutableRouterOptions("model", keyOrOptions);
|
|
6352
7781
|
this.runtime.setModelOptions(this.modelName, keyOrOptions);
|
|
6353
7782
|
}
|
|
@@ -6375,6 +7804,7 @@ import { isNumber as _isNumber, normalizeUrlPath as normalizeUrlPath3, orderBy a
|
|
|
6375
7804
|
// src/core-data.ts
|
|
6376
7805
|
var DataCore = class {
|
|
6377
7806
|
constructor(req) {
|
|
7807
|
+
this.cachedPermissions = null;
|
|
6378
7808
|
this.req = req;
|
|
6379
7809
|
this.caches = {
|
|
6380
7810
|
baseFilter: new Cache()
|
|
@@ -6468,13 +7898,15 @@ var DataCore = class {
|
|
|
6468
7898
|
});
|
|
6469
7899
|
}
|
|
6470
7900
|
getPermissions() {
|
|
7901
|
+
if (this.cachedPermissions) return this.cachedPermissions;
|
|
6471
7902
|
return getResolvedRequestPermissions(this.req);
|
|
6472
7903
|
}
|
|
6473
7904
|
async setPermissions() {
|
|
6474
7905
|
await setResolvedRequestPermissions(this.req);
|
|
7906
|
+
this.cachedPermissions = getResolvedRequestPermissions(this.req);
|
|
6475
7907
|
}
|
|
6476
7908
|
async canActivate(routeGuard) {
|
|
6477
|
-
return
|
|
7909
|
+
return evaluateRouteGuard(this.req, this.getGlobalPermissions(), routeGuard);
|
|
6478
7910
|
}
|
|
6479
7911
|
async isAllowed(dataName, access) {
|
|
6480
7912
|
const operationAccess = getDataOption(dataName, `operationAccess.${access}`);
|
|
@@ -6490,6 +7922,7 @@ var DataCore = class {
|
|
|
6490
7922
|
return this.getService(dataName);
|
|
6491
7923
|
}
|
|
6492
7924
|
getGlobalPermissions() {
|
|
7925
|
+
if (this.cachedPermissions) return this.cachedPermissions;
|
|
6493
7926
|
return getGlobalPermissions(this.req);
|
|
6494
7927
|
}
|
|
6495
7928
|
};
|
|
@@ -6499,6 +7932,7 @@ var createSetDataCore = (runtime = defaultRuntime) => {
|
|
|
6499
7932
|
await initializeAclRequest({
|
|
6500
7933
|
req,
|
|
6501
7934
|
flag: DATA_MIDDLEWARE,
|
|
7935
|
+
runtime,
|
|
6502
7936
|
createCore: (request) => new DataCore(request),
|
|
6503
7937
|
assignCore: (core) => {
|
|
6504
7938
|
req.dacl = core;
|
|
@@ -6781,7 +8215,7 @@ var RootRouter = class {
|
|
|
6781
8215
|
|
|
6782
8216
|
// src/routers/data-router.ts
|
|
6783
8217
|
import JsonRouter7 from "@web-ts-toolkit/express-json-router";
|
|
6784
|
-
import { isPlainObject as
|
|
8218
|
+
import { isPlainObject as isPlainObject19, isString as isString9, isUndefined as isUndefined3, normalizeUrlPath as normalizeUrlPath4 } from "@web-ts-toolkit/utils";
|
|
6785
8219
|
var clientErrors4 = JsonRouter7.clientErrors;
|
|
6786
8220
|
function setOption2(parentKey, optionKey, option) {
|
|
6787
8221
|
const key = isUndefined3(option) ? parentKey : `${parentKey}.${optionKey}`;
|
|
@@ -6803,6 +8237,11 @@ var DataRouter = class {
|
|
|
6803
8237
|
this,
|
|
6804
8238
|
"baseFilter"
|
|
6805
8239
|
);
|
|
8240
|
+
/**
|
|
8241
|
+
* Trusted filter replacements applied before base-filter composition.
|
|
8242
|
+
* They may replace ordinary filters, but cannot replace an existing `false` denial.
|
|
8243
|
+
* Returning `false` denies the transaction.
|
|
8244
|
+
*/
|
|
6806
8245
|
this.overrideFilter = setOption2.bind(this, "overrideFilter");
|
|
6807
8246
|
this.decorate = setOption2.bind(
|
|
6808
8247
|
this,
|
|
@@ -6977,7 +8416,7 @@ var DataRouter = class {
|
|
|
6977
8416
|
assertMutableRouterOption("data", keyOrOptions);
|
|
6978
8417
|
this.runtime.setDataOption(this.dataName, keyOrOptions, value);
|
|
6979
8418
|
}
|
|
6980
|
-
if (arguments.length === 1 &&
|
|
8419
|
+
if (arguments.length === 1 && isPlainObject19(keyOrOptions)) {
|
|
6981
8420
|
assertMutableRouterOptions("data", keyOrOptions);
|
|
6982
8421
|
this.runtime.setDataOptions(this.dataName, keyOrOptions);
|
|
6983
8422
|
}
|
|
@@ -7124,7 +8563,7 @@ function createRuntimeApi(runtime) {
|
|
|
7124
8563
|
if (arguments.length === 2 && isString10(keyOrOptions)) {
|
|
7125
8564
|
return runtime.setGlobalOption(keyOrOptions, value);
|
|
7126
8565
|
}
|
|
7127
|
-
if (arguments.length === 1 &&
|
|
8566
|
+
if (arguments.length === 1 && isPlainObject20(keyOrOptions)) {
|
|
7128
8567
|
return runtime.setGlobalOptions(keyOrOptions);
|
|
7129
8568
|
}
|
|
7130
8569
|
};
|