@web-ts-toolkit/access-router 0.32.0 → 0.33.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 +101 -37
- package/advanced.d.mts +12 -1444
- package/advanced.d.ts +12 -1444
- package/advanced.js +1167 -269
- package/advanced.mjs +1178 -269
- package/index.d.mts +93 -1264
- package/index.d.ts +93 -1264
- package/index.js +1465 -489
- package/index.mjs +1354 -388
- package/llms.txt +34 -11
- package/package.json +10 -5
- package/parsers-D9B9dS0p.d.mts +1702 -0
- package/parsers-D9B9dS0p.d.ts +1702 -0
- package/processors.d.mts +72 -1
- package/processors.d.ts +72 -1
- package/processors.js +61 -13
- package/processors.mjs +62 -14
package/index.mjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { isPlainObject as
|
|
2
|
+
import { isPlainObject as isPlainObject17, 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 isArray11, isFunction as isFunction6, isPlainObject as isPlainObject14, isString as isString7 } from "@web-ts-toolkit/utils";
|
|
7
7
|
|
|
8
8
|
// src/core.ts
|
|
9
9
|
import {
|
|
10
|
-
castArray as
|
|
10
|
+
castArray as castArray4,
|
|
11
11
|
compact as compact3,
|
|
12
12
|
forEach as forEach7,
|
|
13
|
-
isArray as
|
|
13
|
+
isArray as isArray10,
|
|
14
14
|
isBoolean as isBoolean5,
|
|
15
15
|
isFunction as isFunction5,
|
|
16
16
|
isString as isString6,
|
|
@@ -25,11 +25,11 @@ import {
|
|
|
25
25
|
addLeadingSlash,
|
|
26
26
|
forEach as forEach2,
|
|
27
27
|
get as get3,
|
|
28
|
-
isArray as
|
|
28
|
+
isArray as isArray5,
|
|
29
29
|
isBoolean,
|
|
30
30
|
isFunction,
|
|
31
31
|
isNil as isNil2,
|
|
32
|
-
isPlainObject as
|
|
32
|
+
isPlainObject as isPlainObject9,
|
|
33
33
|
isString as isString4,
|
|
34
34
|
keys
|
|
35
35
|
} from "@web-ts-toolkit/utils";
|
|
@@ -52,7 +52,13 @@ import { Document } from "mongoose";
|
|
|
52
52
|
import { get, isPlainObject as isPlainObject2, pick, set } from "@web-ts-toolkit/utils";
|
|
53
53
|
|
|
54
54
|
// src/helpers/query.ts
|
|
55
|
-
import { flattenDeep,
|
|
55
|
+
import { flattenDeep, isNil, isPlainObject, isString, reduce } from "@web-ts-toolkit/utils";
|
|
56
|
+
var normalizeSafeInteger = (value, min) => {
|
|
57
|
+
if (isNil(value)) return null;
|
|
58
|
+
const parsed = Number(value);
|
|
59
|
+
if (!Number.isSafeInteger(parsed) || parsed < min) return null;
|
|
60
|
+
return parsed;
|
|
61
|
+
};
|
|
56
62
|
function genPagination({
|
|
57
63
|
skip,
|
|
58
64
|
limit,
|
|
@@ -60,13 +66,21 @@ function genPagination({
|
|
|
60
66
|
pageSize
|
|
61
67
|
}, hardLimit) {
|
|
62
68
|
let _skip = 0;
|
|
63
|
-
let _limit =
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
let _limit = normalizeSafeInteger(limit ?? pageSize, 1) ?? hardLimit;
|
|
70
|
+
if (!Number.isSafeInteger(_limit) || _limit > hardLimit) _limit = hardLimit;
|
|
71
|
+
const normalizedSkip = normalizeSafeInteger(skip, 0);
|
|
72
|
+
const normalizedPage = normalizeSafeInteger(page, 0);
|
|
73
|
+
if (normalizedSkip !== null) {
|
|
74
|
+
_skip = normalizedSkip;
|
|
75
|
+
} else if (normalizedPage !== null) {
|
|
76
|
+
const npage = normalizedPage;
|
|
77
|
+
if (npage > 1) {
|
|
78
|
+
const derivedSkip = (npage - 1) * _limit;
|
|
79
|
+
_skip = Number.isSafeInteger(derivedSkip) ? derivedSkip : Number.MAX_SAFE_INTEGER;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!Number.isSafeInteger(_skip) || _skip < 0) {
|
|
83
|
+
return { skip: 0, limit: _limit };
|
|
70
84
|
}
|
|
71
85
|
return { skip: _skip, limit: _limit };
|
|
72
86
|
}
|
|
@@ -135,6 +149,7 @@ async function populateDoc(doc, target) {
|
|
|
135
149
|
if (isPromise(p)) return p;
|
|
136
150
|
return "execPopulate" in p && p.execPopulate?.();
|
|
137
151
|
}
|
|
152
|
+
var normalizeSubPopulate = (populate) => isArray(populate) ? populate.map((item) => isString2(item) ? { path: item } : item) : isString2(populate) ? { path: populate } : populate ?? [];
|
|
138
153
|
var genSubPopulate = (sub, popul) => {
|
|
139
154
|
if (!popul) return [];
|
|
140
155
|
let populate = isArray(popul) ? popul : [popul];
|
|
@@ -150,7 +165,7 @@ var genSubPopulate = (sub, popul) => {
|
|
|
150
165
|
|
|
151
166
|
// src/helpers/errors.ts
|
|
152
167
|
import JsonRouter from "@web-ts-toolkit/express-json-router";
|
|
153
|
-
var { BadRequestError, ForbiddenError, NotFoundError, UnprocessableEntityError } = JsonRouter.clientErrors;
|
|
168
|
+
var { BadRequestError, ForbiddenError, NotFoundError, UnauthorizedError, UnprocessableEntityError } = JsonRouter.clientErrors;
|
|
154
169
|
function mapCodeToMessage(code) {
|
|
155
170
|
switch (code) {
|
|
156
171
|
case "success" /* Success */:
|
|
@@ -159,6 +174,8 @@ function mapCodeToMessage(code) {
|
|
|
159
174
|
return "Created";
|
|
160
175
|
case "bad_request" /* BadRequest */:
|
|
161
176
|
return "Bad Request";
|
|
177
|
+
case "unauthorized" /* Unauthorized */:
|
|
178
|
+
return "Unauthorized";
|
|
162
179
|
case "forbidden" /* Forbidden */:
|
|
163
180
|
return "Forbidden";
|
|
164
181
|
case "not_found" /* NotFound */:
|
|
@@ -175,6 +192,8 @@ function mapCodeToStatusCode(code) {
|
|
|
175
192
|
return 201 /* Created */;
|
|
176
193
|
case "bad_request" /* BadRequest */:
|
|
177
194
|
return 400 /* BadRequest */;
|
|
195
|
+
case "unauthorized" /* Unauthorized */:
|
|
196
|
+
return 401 /* Unauthorized */;
|
|
178
197
|
case "forbidden" /* Forbidden */:
|
|
179
198
|
return 403 /* Forbidden */;
|
|
180
199
|
case "not_found" /* NotFound */:
|
|
@@ -185,11 +204,14 @@ function mapCodeToStatusCode(code) {
|
|
|
185
204
|
}
|
|
186
205
|
function handleResultError(result) {
|
|
187
206
|
if (result.success) return;
|
|
207
|
+
if (result.kind !== "error") return;
|
|
188
208
|
const { code, errors = [] } = result;
|
|
189
209
|
const errorOptions = { errors };
|
|
190
210
|
switch (code) {
|
|
191
211
|
case "bad_request" /* BadRequest */:
|
|
192
212
|
throw new BadRequestError("Bad Request", errorOptions);
|
|
213
|
+
case "unauthorized" /* Unauthorized */:
|
|
214
|
+
throw new UnauthorizedError("Unauthorized", errorOptions);
|
|
193
215
|
case "forbidden" /* Forbidden */:
|
|
194
216
|
throw new ForbiddenError("Forbidden", errorOptions);
|
|
195
217
|
case "not_found" /* NotFound */:
|
|
@@ -516,22 +538,102 @@ function buildOpenApiSpec(routes, options) {
|
|
|
516
538
|
};
|
|
517
539
|
}
|
|
518
540
|
|
|
541
|
+
// src/openapi/errors.ts
|
|
542
|
+
var OpenApiCollisionError = class extends Error {
|
|
543
|
+
constructor(message, details) {
|
|
544
|
+
super(message);
|
|
545
|
+
this.name = "OpenApiCollisionError";
|
|
546
|
+
this.collisionKind = details.collisionKind;
|
|
547
|
+
this.method = details.method;
|
|
548
|
+
this.path = details.path;
|
|
549
|
+
this.operationId = details.operationId;
|
|
550
|
+
this.existing = details.existing;
|
|
551
|
+
this.incoming = details.incoming;
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
|
|
519
555
|
// src/openapi/registry.ts
|
|
556
|
+
var isPlainObject7 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
557
|
+
var stableKey = (value) => {
|
|
558
|
+
if (value === null || typeof value === "undefined") return "null";
|
|
559
|
+
if (typeof value === "function") return "[Function]";
|
|
560
|
+
if (typeof value !== "object") return JSON.stringify(value);
|
|
561
|
+
if (Array.isArray(value)) {
|
|
562
|
+
return `[${value.map(stableKey).join(",")}]`;
|
|
563
|
+
}
|
|
564
|
+
const obj = value;
|
|
565
|
+
return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${stableKey(obj[k])}`).join(",")}}`;
|
|
566
|
+
};
|
|
567
|
+
var descriptorsEqual = (a, b) => {
|
|
568
|
+
return a.method === b.method && a.path === b.path && (a.operationId ?? "") === (b.operationId ?? "") && (a.acl ?? "") === (b.acl ?? "") && stableKey(a.query) === stableKey(b.query) && stableKey(a.body) === stableKey(b.body) && stableKey(a.pathParams) === stableKey(b.pathParams) && stableKey(a.responses) === stableKey(b.responses) && stableKey(a.tags) === stableKey(b.tags) && (a.summary ?? "") === (b.summary ?? "") && (a.description ?? "") === (b.description ?? "") && Boolean(a.deprecated) === Boolean(b.deprecated);
|
|
569
|
+
};
|
|
520
570
|
var OpenApiRegistry = class {
|
|
521
|
-
constructor() {
|
|
571
|
+
constructor(options = {}) {
|
|
522
572
|
this.routes = [];
|
|
573
|
+
this.options = {
|
|
574
|
+
rejectConflicts: options.rejectConflicts ?? true,
|
|
575
|
+
rejectDuplicateOperationIds: options.rejectDuplicateOperationIds ?? true
|
|
576
|
+
};
|
|
523
577
|
}
|
|
524
578
|
register(route) {
|
|
525
579
|
const existingIndex = this.routes.findIndex((item) => item.method === route.method && item.path === route.path);
|
|
526
580
|
if (existingIndex === -1) {
|
|
581
|
+
this.assertOperationIdAvailable(route);
|
|
527
582
|
this.routes.push(route);
|
|
528
583
|
return;
|
|
529
584
|
}
|
|
585
|
+
const existing = this.routes[existingIndex];
|
|
586
|
+
if (route.allowReplace) {
|
|
587
|
+
this.assertOperationIdAvailable(route, existing);
|
|
588
|
+
this.routes[existingIndex] = route;
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (descriptorsEqual(existing, route) && (route.idempotent || isPlainObject7(existing) && existing.idempotent)) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (this.options.rejectConflicts) {
|
|
595
|
+
throw new OpenApiCollisionError(
|
|
596
|
+
`OpenAPI route collision: ${route.method.toUpperCase()} ${route.path} is already registered` + (existing.operationId ? ` as operationId="${existing.operationId}"` : "") + (route.operationId ? `; new registration uses operationId="${route.operationId}"` : "") + ". Set allowReplace:true on the new descriptor to override, or fix the duplicate registration.",
|
|
597
|
+
{
|
|
598
|
+
collisionKind: "path",
|
|
599
|
+
method: route.method,
|
|
600
|
+
path: route.path,
|
|
601
|
+
existing,
|
|
602
|
+
incoming: route
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
}
|
|
530
606
|
this.routes[existingIndex] = route;
|
|
531
607
|
}
|
|
608
|
+
assertOperationIdAvailable(route, replacing) {
|
|
609
|
+
if (!this.options.rejectDuplicateOperationIds) return;
|
|
610
|
+
if (!route.operationId) return;
|
|
611
|
+
const clash = this.routes.find(
|
|
612
|
+
(item) => item.operationId && item.operationId === route.operationId && item !== replacing
|
|
613
|
+
);
|
|
614
|
+
if (clash) {
|
|
615
|
+
if (route.idempotent && clash.idempotent) return;
|
|
616
|
+
throw new OpenApiCollisionError(
|
|
617
|
+
`OpenAPI operationId collision: "${route.operationId}" is already bound to ${clash.method.toUpperCase()} ${clash.path}. Each operationId must be unique across the spec.`,
|
|
618
|
+
{
|
|
619
|
+
collisionKind: "operationId",
|
|
620
|
+
operationId: route.operationId,
|
|
621
|
+
existing: clash,
|
|
622
|
+
incoming: route
|
|
623
|
+
}
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
532
627
|
getRoutes() {
|
|
533
628
|
return [...this.routes];
|
|
534
629
|
}
|
|
630
|
+
clear() {
|
|
631
|
+
this.routes.length = 0;
|
|
632
|
+
}
|
|
633
|
+
setStrictMode(enabled) {
|
|
634
|
+
this.options.rejectConflicts = enabled;
|
|
635
|
+
this.options.rejectDuplicateOperationIds = enabled;
|
|
636
|
+
}
|
|
535
637
|
getSpec(info) {
|
|
536
638
|
return buildOpenApiSpec(this.routes, info);
|
|
537
639
|
}
|
|
@@ -553,8 +655,8 @@ var defaultLogger = createLogger({
|
|
|
553
655
|
format.label({ label: "[WTT]" }),
|
|
554
656
|
format.timestamp(),
|
|
555
657
|
format.printf(({ level, label, message, timestamp, stack, ...meta }) => {
|
|
556
|
-
const formattedLabel = colorizer.colorize(level, label);
|
|
557
|
-
const formattedLevel = colorizer.colorize(level, `[${level.toUpperCase()}]`);
|
|
658
|
+
const formattedLabel = colorizer.colorize(level, String(label));
|
|
659
|
+
const formattedLevel = colorizer.colorize(level, `[${String(level).toUpperCase()}]`);
|
|
558
660
|
const formattedMessage = colorizer.colorize(level, inspectValue(stack ?? message));
|
|
559
661
|
const formattedMeta = Object.keys(meta).length > 0 ? ` ${inspectValue(meta)}` : "";
|
|
560
662
|
return `${formattedLabel} ${timestamp} ${formattedLevel} ${formattedMessage}${formattedMeta}`;
|
|
@@ -589,8 +691,8 @@ var OptionsManager = class {
|
|
|
589
691
|
this.currentOptions = new Proxy({}, {
|
|
590
692
|
set(target, key, value) {
|
|
591
693
|
const keystr = String(key);
|
|
592
|
-
const oldvalue = target[
|
|
593
|
-
target[
|
|
694
|
+
const oldvalue = target[keystr];
|
|
695
|
+
target[keystr] = value;
|
|
594
696
|
_this.listeners[keystr] && _this.listeners[keystr].call(_this, value, keystr, target, oldvalue);
|
|
595
697
|
return true;
|
|
596
698
|
}
|
|
@@ -618,8 +720,116 @@ var OptionsManager = class {
|
|
|
618
720
|
}
|
|
619
721
|
};
|
|
620
722
|
|
|
723
|
+
// src/request-complexity.ts
|
|
724
|
+
import { isArray as isArray4, isPlainObject as isPlainObject8 } from "@web-ts-toolkit/utils";
|
|
725
|
+
var defaultRequestComplexity = {
|
|
726
|
+
maxDepth: 8,
|
|
727
|
+
maxNodes: 500,
|
|
728
|
+
maxLogicalClauses: 50,
|
|
729
|
+
maxInValues: 100,
|
|
730
|
+
maxBulkItems: 100,
|
|
731
|
+
maxIncludeCount: 10,
|
|
732
|
+
maxSubQueryCount: 10,
|
|
733
|
+
maxBulkConcurrency: 10
|
|
734
|
+
};
|
|
735
|
+
var normalizePositiveInteger = (value, fallback) => {
|
|
736
|
+
return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : fallback;
|
|
737
|
+
};
|
|
738
|
+
var resolveRequestComplexity = (options) => ({
|
|
739
|
+
maxDepth: normalizePositiveInteger(options?.maxDepth, defaultRequestComplexity.maxDepth),
|
|
740
|
+
maxNodes: normalizePositiveInteger(options?.maxNodes, defaultRequestComplexity.maxNodes),
|
|
741
|
+
maxLogicalClauses: normalizePositiveInteger(options?.maxLogicalClauses, defaultRequestComplexity.maxLogicalClauses),
|
|
742
|
+
maxInValues: normalizePositiveInteger(options?.maxInValues, defaultRequestComplexity.maxInValues),
|
|
743
|
+
maxBulkItems: normalizePositiveInteger(options?.maxBulkItems, defaultRequestComplexity.maxBulkItems),
|
|
744
|
+
maxIncludeCount: normalizePositiveInteger(options?.maxIncludeCount, defaultRequestComplexity.maxIncludeCount),
|
|
745
|
+
maxSubQueryCount: normalizePositiveInteger(options?.maxSubQueryCount, defaultRequestComplexity.maxSubQueryCount),
|
|
746
|
+
maxBulkConcurrency: normalizePositiveInteger(
|
|
747
|
+
options?.maxBulkConcurrency,
|
|
748
|
+
defaultRequestComplexity.maxBulkConcurrency
|
|
749
|
+
)
|
|
750
|
+
});
|
|
751
|
+
function validateRequestComplexity(value, options, scope = "request") {
|
|
752
|
+
const limits = resolveRequestComplexity(options);
|
|
753
|
+
const errors = [];
|
|
754
|
+
let nodes = 0;
|
|
755
|
+
let logicalClauses = 0;
|
|
756
|
+
let includeCount = 0;
|
|
757
|
+
let subQueryCount = 0;
|
|
758
|
+
const pointer = (path) => path.length === 0 ? "#" : `#/${path.join("/")}`;
|
|
759
|
+
const pushError = (detail, path) => {
|
|
760
|
+
if (errors.length === 0) {
|
|
761
|
+
errors.push({ detail, pointer: pointer(path) });
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
const visit = (current, path, depth, parentKey) => {
|
|
765
|
+
if (errors.length > 0) return;
|
|
766
|
+
nodes += 1;
|
|
767
|
+
if (nodes > limits.maxNodes) {
|
|
768
|
+
pushError(`Request exceeds maximum node budget of ${limits.maxNodes}`, path);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (depth > limits.maxDepth) {
|
|
772
|
+
pushError(`Request exceeds maximum depth of ${limits.maxDepth}`, path);
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (isArray4(current)) {
|
|
776
|
+
if (parentKey === "$and" || parentKey === "$or" || parentKey === "$nor") {
|
|
777
|
+
logicalClauses += current.length;
|
|
778
|
+
if (logicalClauses > limits.maxLogicalClauses) {
|
|
779
|
+
pushError(`Filter exceeds maximum logical clause budget of ${limits.maxLogicalClauses}`, path);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (parentKey === "$in") {
|
|
784
|
+
if (current.length > limits.maxInValues) {
|
|
785
|
+
pushError(`Filter exceeds maximum $in values of ${limits.maxInValues}`, path);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
for (let index = 0; index < current.length; index++) {
|
|
790
|
+
visit(current[index], path.concat(index), depth + 1, parentKey);
|
|
791
|
+
if (errors.length > 0) return;
|
|
792
|
+
}
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
if (!isPlainObject8(current)) return;
|
|
796
|
+
for (const [key, child] of Object.entries(current)) {
|
|
797
|
+
if (key === "__proto__" || key === "prototype" || key === "constructor") {
|
|
798
|
+
pushError(`Unsupported key: ${key}`, path.concat(key));
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
if (scope === "request" && key === "include") {
|
|
802
|
+
includeCount += isArray4(child) ? child.length : 1;
|
|
803
|
+
if (includeCount > limits.maxIncludeCount) {
|
|
804
|
+
pushError(`Request exceeds maximum include count of ${limits.maxIncludeCount}`, path.concat(key));
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (key === "$$sq") {
|
|
809
|
+
subQueryCount += 1;
|
|
810
|
+
if (subQueryCount > limits.maxSubQueryCount) {
|
|
811
|
+
pushError(`Request exceeds maximum subquery count of ${limits.maxSubQueryCount}`, path.concat(key));
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
visit(child, path.concat(key), depth + 1, key);
|
|
815
|
+
if (errors.length > 0) return;
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
visit(child, path.concat(key), depth + 1, key);
|
|
819
|
+
if (errors.length > 0) return;
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
visit(value, [], 0);
|
|
823
|
+
return errors;
|
|
824
|
+
}
|
|
825
|
+
|
|
621
826
|
// src/runtime.ts
|
|
622
|
-
|
|
827
|
+
var mongooseJsonSchemaInitialized = false;
|
|
828
|
+
var ensureMongooseJsonSchemaInitialized = () => {
|
|
829
|
+
if (mongooseJsonSchemaInitialized) return;
|
|
830
|
+
mschema2Jsonschema(mongoose);
|
|
831
|
+
mongooseJsonSchemaInitialized = true;
|
|
832
|
+
};
|
|
623
833
|
var pluralize = mongoose.pluralize();
|
|
624
834
|
var FIELD_ACCESS_KEYS = ["list", "create", "read", "update"];
|
|
625
835
|
var defaultModelOptions = {
|
|
@@ -628,6 +838,7 @@ var defaultModelOptions = {
|
|
|
628
838
|
};
|
|
629
839
|
var defaultDataOptions = {
|
|
630
840
|
basePath: null,
|
|
841
|
+
parentPath: "/",
|
|
631
842
|
queryRouteSegment: "__query"
|
|
632
843
|
};
|
|
633
844
|
var normalizeBasePath = (name, value) => {
|
|
@@ -643,9 +854,9 @@ var hasModelPermission = (value, modelPermissionPrefix) => {
|
|
|
643
854
|
if (isString4(value)) {
|
|
644
855
|
return value.trim().split(" ").some((item) => item.startsWith(modelPermissionPrefix));
|
|
645
856
|
}
|
|
646
|
-
if (
|
|
857
|
+
if (isArray5(value)) {
|
|
647
858
|
return value.some((item) => {
|
|
648
|
-
if (isString4(item) ||
|
|
859
|
+
if (isString4(item) || isArray5(item)) {
|
|
649
860
|
return hasModelPermission(item, modelPermissionPrefix);
|
|
650
861
|
}
|
|
651
862
|
return true;
|
|
@@ -659,13 +870,13 @@ var classifyPermissionSchema = (permissionSchema, modelPermissionPrefix) => {
|
|
|
659
870
|
const modelPermissionKeys = {};
|
|
660
871
|
forEach2(schemaKeys, (schemaKey) => {
|
|
661
872
|
const schemaRule = permissionSchema[schemaKey];
|
|
662
|
-
const ruleEntries =
|
|
873
|
+
const ruleEntries = isPlainObject9(schemaRule) ? Object.entries(schemaRule) : FIELD_ACCESS_KEYS.map((accessKey) => [accessKey, schemaRule]);
|
|
663
874
|
for (let x = 0; x < ruleEntries.length; x++) {
|
|
664
875
|
const [accessKey, value] = ruleEntries[x];
|
|
665
|
-
if (!
|
|
876
|
+
if (!isArray5(globalPermissionKeys[accessKey])) {
|
|
666
877
|
globalPermissionKeys[accessKey] = [];
|
|
667
878
|
}
|
|
668
|
-
if (!
|
|
879
|
+
if (!isArray5(modelPermissionKeys[accessKey])) {
|
|
669
880
|
modelPermissionKeys[accessKey] = [];
|
|
670
881
|
}
|
|
671
882
|
if (isBoolean(value)) {
|
|
@@ -690,7 +901,8 @@ var AccessRuntime = class {
|
|
|
690
901
|
this.globalOptions = new OptionsManager({
|
|
691
902
|
requestPermissionField: "_permissions",
|
|
692
903
|
globalPermissions: () => ({}),
|
|
693
|
-
logger: defaultLogger
|
|
904
|
+
logger: defaultLogger,
|
|
905
|
+
requestComplexity: defaultRequestComplexity
|
|
694
906
|
}).build();
|
|
695
907
|
this.defaultModelOptions = new OptionsManager({
|
|
696
908
|
listHardLimit: 1e3,
|
|
@@ -709,14 +921,50 @@ var AccessRuntime = class {
|
|
|
709
921
|
this.modelRefs = {};
|
|
710
922
|
this.modelSubs = {};
|
|
711
923
|
this.modelAtts = {};
|
|
924
|
+
this.modelInstances = {};
|
|
712
925
|
this.openApiRegistry = new OpenApiRegistry();
|
|
926
|
+
ensureMongooseJsonSchemaInitialized();
|
|
927
|
+
}
|
|
928
|
+
registerModelInstance(modelName, model) {
|
|
929
|
+
if (!modelName || typeof modelName !== "string") {
|
|
930
|
+
throw new TypeError(`registerModelInstance: modelName must be a non-empty string, received ${typeof modelName}`);
|
|
931
|
+
}
|
|
932
|
+
const existing = this.modelInstances[modelName];
|
|
933
|
+
if (existing && existing !== model) {
|
|
934
|
+
throw new Error(
|
|
935
|
+
`Runtime model registry conflict: model "${modelName}" is already registered to a different mongoose.Model instance on this runtime. Use a distinct model name or a separate runtime.`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
this.modelInstances[modelName] = model;
|
|
939
|
+
}
|
|
940
|
+
hasModelInstance(modelName) {
|
|
941
|
+
return modelName in this.modelInstances;
|
|
942
|
+
}
|
|
943
|
+
getModelInstance(modelName) {
|
|
944
|
+
const registered = this.modelInstances[modelName];
|
|
945
|
+
if (registered) return registered;
|
|
946
|
+
const global = mongoose.models[modelName];
|
|
947
|
+
return global ?? null;
|
|
713
948
|
}
|
|
714
949
|
registerOpenApiRoute(route) {
|
|
715
950
|
this.openApiRegistry.register(route);
|
|
716
951
|
}
|
|
952
|
+
/**
|
|
953
|
+
* Enable strict OpenAPI collision detection for the remainder of this
|
|
954
|
+
* runtime's lifetime. After this call, conflicting method/path
|
|
955
|
+
* registrations and duplicate operationIds throw {@link OpenApiCollisionError}
|
|
956
|
+
* instead of silently replacing. Existing already-registered routes are
|
|
957
|
+
* not retroactively re-validated.
|
|
958
|
+
*/
|
|
959
|
+
enableOpenApiCollisionDetection() {
|
|
960
|
+
this.openApiRegistry.setStrictMode(true);
|
|
961
|
+
}
|
|
717
962
|
getOpenApiRoutes() {
|
|
718
963
|
return this.openApiRegistry.getRoutes();
|
|
719
964
|
}
|
|
965
|
+
clearOpenApiRoutes() {
|
|
966
|
+
this.openApiRegistry.clear();
|
|
967
|
+
}
|
|
720
968
|
getOpenApiSpec(info) {
|
|
721
969
|
return this.openApiRegistry.getSpec(info);
|
|
722
970
|
}
|
|
@@ -745,7 +993,7 @@ var AccessRuntime = class {
|
|
|
745
993
|
return this.defaultModelOptions.get(key, defaultValue);
|
|
746
994
|
}
|
|
747
995
|
createModelOptions(modelName) {
|
|
748
|
-
const model = mongoose.model(modelName);
|
|
996
|
+
const model = this.getModelInstance(modelName) ?? mongoose.model(modelName);
|
|
749
997
|
const manager = new OptionsManager({
|
|
750
998
|
...defaultModelOptions,
|
|
751
999
|
modelName
|
|
@@ -861,7 +1109,7 @@ var AccessRuntime = class {
|
|
|
861
1109
|
if (modelName in this.modelRefs && modelName in this.modelSubs && modelName in this.modelAtts) {
|
|
862
1110
|
return;
|
|
863
1111
|
}
|
|
864
|
-
const model =
|
|
1112
|
+
const model = this.getModelInstance(modelName);
|
|
865
1113
|
if (!model) {
|
|
866
1114
|
this.modelRefs[modelName] = this.modelRefs[modelName] ?? {};
|
|
867
1115
|
this.modelSubs[modelName] = this.modelSubs[modelName] ?? [];
|
|
@@ -891,8 +1139,6 @@ var defaultRuntime = new AccessRuntime();
|
|
|
891
1139
|
|
|
892
1140
|
// src/runtime-context.ts
|
|
893
1141
|
import { AsyncLocalStorage } from "async_hooks";
|
|
894
|
-
import mongoose2 from "mongoose";
|
|
895
|
-
var ACCESS_RUNTIME = /* @__PURE__ */ Symbol("access-runtime");
|
|
896
1142
|
var runtimeStorage = new AsyncLocalStorage();
|
|
897
1143
|
function runWithRuntime(runtime, callback) {
|
|
898
1144
|
return runtimeStorage.run(runtime, callback);
|
|
@@ -900,16 +1146,6 @@ function runWithRuntime(runtime, callback) {
|
|
|
900
1146
|
function getActiveRuntime() {
|
|
901
1147
|
return runtimeStorage.getStore() ?? null;
|
|
902
1148
|
}
|
|
903
|
-
function attachRuntimeToModel(modelName, runtime) {
|
|
904
|
-
const model = mongoose2.models[modelName];
|
|
905
|
-
if (model) {
|
|
906
|
-
model[ACCESS_RUNTIME] = runtime;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
function getRuntimeForModelName(modelName) {
|
|
910
|
-
const model = mongoose2.models[modelName];
|
|
911
|
-
return model?.[ACCESS_RUNTIME] ?? null;
|
|
912
|
-
}
|
|
913
1149
|
|
|
914
1150
|
// src/options/global-options.ts
|
|
915
1151
|
var getRuntime = () => getActiveRuntime() ?? defaultRuntime;
|
|
@@ -927,27 +1163,36 @@ var getGlobalOption = (key, defaultValue) => {
|
|
|
927
1163
|
};
|
|
928
1164
|
|
|
929
1165
|
// src/options/model-options.ts
|
|
930
|
-
var getRuntime2 = (
|
|
1166
|
+
var getRuntime2 = () => getActiveRuntime() ?? defaultRuntime;
|
|
931
1167
|
var setModelOptions = (modelName, options) => {
|
|
932
|
-
getRuntime2(
|
|
1168
|
+
getRuntime2().setModelOptions(modelName, options);
|
|
933
1169
|
};
|
|
934
1170
|
var setModelOption = (modelName, key, value) => {
|
|
935
|
-
getRuntime2(
|
|
1171
|
+
getRuntime2().setModelOption(modelName, key, value);
|
|
936
1172
|
};
|
|
937
1173
|
var getModelOptions = (modelName) => {
|
|
938
|
-
return getRuntime2(
|
|
1174
|
+
return getRuntime2().getModelOptions(modelName);
|
|
939
1175
|
};
|
|
940
1176
|
var getModelOption = (modelName, key, defaultValue) => {
|
|
941
|
-
return getRuntime2(
|
|
1177
|
+
return getRuntime2().getModelOption(modelName, key, defaultValue);
|
|
942
1178
|
};
|
|
943
1179
|
var getExactModelOption = (modelName, key) => {
|
|
944
|
-
return getRuntime2(
|
|
1180
|
+
return getRuntime2().getExactModelOption(modelName, key);
|
|
945
1181
|
};
|
|
946
1182
|
var getModelNames = () => {
|
|
947
1183
|
return (getActiveRuntime() ?? defaultRuntime).getModelNames();
|
|
948
1184
|
};
|
|
949
1185
|
var getModelJsonSchema = (modelName) => {
|
|
950
|
-
return getRuntime2(
|
|
1186
|
+
return getRuntime2().getModelJsonSchema(modelName);
|
|
1187
|
+
};
|
|
1188
|
+
var registerModelInstance = (modelName, model) => {
|
|
1189
|
+
getRuntime2().registerModelInstance(modelName, model);
|
|
1190
|
+
};
|
|
1191
|
+
var hasModelInstance = (modelName) => {
|
|
1192
|
+
return getRuntime2().hasModelInstance(modelName);
|
|
1193
|
+
};
|
|
1194
|
+
var getModelInstance = (modelName) => {
|
|
1195
|
+
return getRuntime2().getModelInstance(modelName);
|
|
951
1196
|
};
|
|
952
1197
|
|
|
953
1198
|
// src/options/default-model-options.ts
|
|
@@ -975,12 +1220,12 @@ var getDataOption = (dataName, key, defaultValue) => {
|
|
|
975
1220
|
};
|
|
976
1221
|
|
|
977
1222
|
// src/meta.ts
|
|
978
|
-
var getRuntime5 = (
|
|
1223
|
+
var getRuntime5 = () => getActiveRuntime() ?? defaultRuntime;
|
|
979
1224
|
var getModelRef = (modelName, refPath) => {
|
|
980
|
-
return getRuntime5(
|
|
1225
|
+
return getRuntime5().getModelRef(modelName, refPath);
|
|
981
1226
|
};
|
|
982
1227
|
var getModelSub = (modelName) => {
|
|
983
|
-
return getRuntime5(
|
|
1228
|
+
return getRuntime5().getModelSub(modelName);
|
|
984
1229
|
};
|
|
985
1230
|
|
|
986
1231
|
// src/services/base.ts
|
|
@@ -991,19 +1236,29 @@ import {
|
|
|
991
1236
|
forEach as forEach3,
|
|
992
1237
|
get as get4,
|
|
993
1238
|
intersectionBy,
|
|
994
|
-
isArray as
|
|
995
|
-
isPlainObject as
|
|
1239
|
+
isArray as isArray6,
|
|
1240
|
+
isPlainObject as isPlainObject10,
|
|
996
1241
|
uniq
|
|
997
1242
|
} from "@web-ts-toolkit/utils";
|
|
1243
|
+
var ClientRequestError = class extends Error {
|
|
1244
|
+
constructor(result) {
|
|
1245
|
+
super(String(result.code));
|
|
1246
|
+
this.result = result;
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
998
1249
|
function validateClientFilter(filter2) {
|
|
999
1250
|
const errors = [];
|
|
1251
|
+
const complexityErrors = validateRequestComplexity(filter2, getGlobalOption("requestComplexity"), "filter");
|
|
1252
|
+
if (complexityErrors.length > 0) {
|
|
1253
|
+
return complexityErrors.map((error2) => error2.detail);
|
|
1254
|
+
}
|
|
1000
1255
|
const blockedOperators = /* @__PURE__ */ new Set(["$where", "$expr", "$function", "$accumulator"]);
|
|
1001
1256
|
const visit = (value, path) => {
|
|
1002
|
-
if (
|
|
1257
|
+
if (isArray6(value)) {
|
|
1003
1258
|
value.forEach((item, index) => visit(item, `${path}[${index}]`));
|
|
1004
1259
|
return;
|
|
1005
1260
|
}
|
|
1006
|
-
if (!
|
|
1261
|
+
if (!isPlainObject10(value)) return;
|
|
1007
1262
|
Object.entries(value).forEach(([key, child]) => {
|
|
1008
1263
|
const nextPath = path ? `${path}.${key}` : key;
|
|
1009
1264
|
if (blockedOperators.has(key)) {
|
|
@@ -1097,6 +1352,31 @@ var Base = class {
|
|
|
1097
1352
|
validateClientFilter(filter2) {
|
|
1098
1353
|
return validateClientFilter(filter2);
|
|
1099
1354
|
}
|
|
1355
|
+
getRequestComplexity() {
|
|
1356
|
+
return resolveRequestComplexity(getGlobalOption("requestComplexity"));
|
|
1357
|
+
}
|
|
1358
|
+
getClientRequestErrorResult(error2) {
|
|
1359
|
+
return error2 instanceof ClientRequestError ? error2.result : null;
|
|
1360
|
+
}
|
|
1361
|
+
throwClientRequestError(code, detail) {
|
|
1362
|
+
throw new ClientRequestError({
|
|
1363
|
+
success: false,
|
|
1364
|
+
kind: "error",
|
|
1365
|
+
code,
|
|
1366
|
+
errors: [{ detail }]
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
async getAuthorizedTargetService(modelName, op) {
|
|
1370
|
+
const runtime = getActiveRuntime();
|
|
1371
|
+
if (runtime && !runtime.hasModel(modelName)) {
|
|
1372
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, `Model ${modelName} not found`);
|
|
1373
|
+
}
|
|
1374
|
+
const allowed = await this.req.macl.isAllowed(modelName, op);
|
|
1375
|
+
if (!allowed) {
|
|
1376
|
+
this.throwClientRequestError("unauthorized" /* Unauthorized */, "Unauthorized");
|
|
1377
|
+
}
|
|
1378
|
+
return this.req.macl.getPublicService(modelName);
|
|
1379
|
+
}
|
|
1100
1380
|
processInclude(include) {
|
|
1101
1381
|
const includes = compact(castArray(include)).filter(({ model, op, path, localField, foreignField }) => {
|
|
1102
1382
|
return model && op && path && localField && foreignField;
|
|
@@ -1119,65 +1399,73 @@ var Base = class {
|
|
|
1119
1399
|
if (!include) return docs;
|
|
1120
1400
|
const includes = compact(castArray(include));
|
|
1121
1401
|
if (includes.length === 0) return docs;
|
|
1122
|
-
const isSingle = !
|
|
1123
|
-
|
|
1402
|
+
const isSingle = !isArray6(docs);
|
|
1403
|
+
let docList = isSingle ? [docs] : docs;
|
|
1124
1404
|
for (let x = 0; x < includes.length; x++) {
|
|
1125
1405
|
const include2 = includes[x];
|
|
1126
1406
|
if (include2.op === "count") {
|
|
1127
|
-
|
|
1407
|
+
docList = await this.includeDocsCount(docList, include2);
|
|
1128
1408
|
} else {
|
|
1129
|
-
|
|
1409
|
+
docList = await this.includeDocsList(docList, include2);
|
|
1130
1410
|
}
|
|
1131
1411
|
}
|
|
1132
|
-
return isSingle ?
|
|
1412
|
+
return isSingle ? docList[0] : docList;
|
|
1133
1413
|
}
|
|
1134
1414
|
async includeDocsList(docs, include) {
|
|
1135
1415
|
const { model, op, path, localField, foreignField, filter: _filters, args = {}, options = {} } = include;
|
|
1136
|
-
const svc = this.
|
|
1137
|
-
if (!svc) return docs;
|
|
1416
|
+
const svc = await this.getAuthorizedTargetService(model, op);
|
|
1138
1417
|
const includeLocalValues = [];
|
|
1139
1418
|
forEach3(docs, (doc, i) => {
|
|
1140
1419
|
includeLocalValues.push(get4(doc, localField));
|
|
1141
1420
|
});
|
|
1142
1421
|
const filter2 = { ..._filters ?? {}, [foreignField]: { $in: flatten(includeLocalValues) } };
|
|
1143
|
-
const
|
|
1422
|
+
const authorizedFilter = await svc.genFilter(op, filter2);
|
|
1423
|
+
const trustedArgs = {
|
|
1424
|
+
...args,
|
|
1425
|
+
overrides: {
|
|
1426
|
+
filter: authorizedFilter
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
const trustedOptions = {
|
|
1144
1430
|
...options,
|
|
1145
1431
|
lean: true,
|
|
1146
1432
|
includePermissions: false,
|
|
1147
1433
|
includeCount: false
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1434
|
+
};
|
|
1435
|
+
const trustedResult = await svc.find(filter2, trustedArgs, trustedOptions);
|
|
1436
|
+
if (!trustedResult.success) return docs;
|
|
1150
1437
|
for (let y = 0; y < docs.length; y++) {
|
|
1151
1438
|
const doc = docs[y];
|
|
1152
1439
|
const localValue = get4(doc, localField);
|
|
1153
1440
|
const filterFn = (row) => intersectionBy(castArray(localValue), castArray(get4(row, foreignField)), String).length > 0;
|
|
1154
|
-
const matches =
|
|
1441
|
+
const matches = trustedResult.data.filter(filterFn);
|
|
1155
1442
|
setDocValue(doc, path, op === "list" ? matches : matches[0]);
|
|
1156
1443
|
}
|
|
1157
1444
|
return docs;
|
|
1158
1445
|
}
|
|
1159
1446
|
async includeDocsCount(docs, include) {
|
|
1160
1447
|
const { model, path, localField, foreignField, filter: _filters, args = {}, options = {} } = include;
|
|
1161
|
-
const svc = this.
|
|
1162
|
-
if (!svc) return docs;
|
|
1448
|
+
const svc = await this.getAuthorizedTargetService(model, "count");
|
|
1163
1449
|
const includeLocalValues = [];
|
|
1164
1450
|
forEach3(docs, (doc) => {
|
|
1165
1451
|
includeLocalValues.push(get4(doc, localField));
|
|
1166
1452
|
});
|
|
1167
1453
|
const filter2 = { ..._filters ?? {}, [foreignField]: { $in: flatten(includeLocalValues) } };
|
|
1168
|
-
const
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
{
|
|
1175
|
-
...options,
|
|
1176
|
-
lean: true,
|
|
1177
|
-
includePermissions: false,
|
|
1178
|
-
includeCount: false
|
|
1454
|
+
const authorizedFilter = await svc.genFilter("list", filter2);
|
|
1455
|
+
const trustedArgs = {
|
|
1456
|
+
...args,
|
|
1457
|
+
select: [foreignField],
|
|
1458
|
+
overrides: {
|
|
1459
|
+
filter: authorizedFilter
|
|
1179
1460
|
}
|
|
1180
|
-
|
|
1461
|
+
};
|
|
1462
|
+
const trustedOptions = {
|
|
1463
|
+
...options,
|
|
1464
|
+
lean: true,
|
|
1465
|
+
includePermissions: false,
|
|
1466
|
+
includeCount: false
|
|
1467
|
+
};
|
|
1468
|
+
const result = await svc.find(filter2, trustedArgs, trustedOptions);
|
|
1181
1469
|
if (!result.success) return docs;
|
|
1182
1470
|
for (let y = 0; y < docs.length; y++) {
|
|
1183
1471
|
const doc = docs[y];
|
|
@@ -1202,26 +1490,29 @@ var Base = class {
|
|
|
1202
1490
|
}
|
|
1203
1491
|
async handleSubQuery(sq, key) {
|
|
1204
1492
|
const { model, op, id, filter: filter2, args, options, sqOptions = {} } = sq;
|
|
1205
|
-
const svc = this.req.macl.getPublicService(model);
|
|
1206
|
-
if (!svc) return null;
|
|
1207
1493
|
let result;
|
|
1208
1494
|
if (op === "list") {
|
|
1209
|
-
|
|
1495
|
+
const svc = await this.getAuthorizedTargetService(model, "list");
|
|
1496
|
+
result = await svc._list(filter2, args, options);
|
|
1210
1497
|
} else if (op === "read") {
|
|
1498
|
+
const svc = await this.getAuthorizedTargetService(model, "read");
|
|
1499
|
+
const readOptions = { ...options, tryList: false };
|
|
1211
1500
|
if (id) {
|
|
1212
|
-
result = await svc.
|
|
1501
|
+
result = await svc._read(id, args, readOptions);
|
|
1213
1502
|
} else if (filter2) {
|
|
1214
|
-
result = await svc.
|
|
1503
|
+
result = await svc._readFilter(filter2, args, readOptions);
|
|
1215
1504
|
} else {
|
|
1216
|
-
|
|
1505
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, `Subquery for field ${key} requires an id or filter`);
|
|
1217
1506
|
}
|
|
1218
1507
|
} else {
|
|
1219
|
-
|
|
1508
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, `Unsupported subquery operation: ${op}`);
|
|
1509
|
+
}
|
|
1510
|
+
if (!result.success) {
|
|
1511
|
+
throw new ClientRequestError(result);
|
|
1220
1512
|
}
|
|
1221
|
-
if (!result.success) return null;
|
|
1222
1513
|
let ret = result.data;
|
|
1223
1514
|
if (sqOptions.path) {
|
|
1224
|
-
ret =
|
|
1515
|
+
ret = isArray6(ret) ? flatten(ret.map((v) => get4(v, sqOptions.path))) : get4(ret, sqOptions.path);
|
|
1225
1516
|
}
|
|
1226
1517
|
if (sqOptions.compact) {
|
|
1227
1518
|
ret = compact(castArray(ret));
|
|
@@ -1239,7 +1530,7 @@ var Base = class {
|
|
|
1239
1530
|
import {
|
|
1240
1531
|
castArray as castArray2,
|
|
1241
1532
|
compact as compact2,
|
|
1242
|
-
isArray as
|
|
1533
|
+
isArray as isArray7,
|
|
1243
1534
|
isBoolean as isBoolean3,
|
|
1244
1535
|
isFunction as isFunction3,
|
|
1245
1536
|
omit,
|
|
@@ -1250,30 +1541,199 @@ import {
|
|
|
1250
1541
|
import { diff } from "just-diff";
|
|
1251
1542
|
|
|
1252
1543
|
// src/model.ts
|
|
1253
|
-
import
|
|
1544
|
+
import mongoose2 from "mongoose";
|
|
1254
1545
|
|
|
1255
|
-
// src/logger.ts
|
|
1256
|
-
var
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1546
|
+
// src/logger-helpers.ts
|
|
1547
|
+
var SENSITIVE_FILTER_KEYS = /* @__PURE__ */ new Set([
|
|
1548
|
+
"password",
|
|
1549
|
+
"pwd",
|
|
1550
|
+
"secret",
|
|
1551
|
+
"token",
|
|
1552
|
+
"access_token",
|
|
1553
|
+
"refreshtoken",
|
|
1554
|
+
"api_key",
|
|
1555
|
+
"apikey",
|
|
1556
|
+
"authorization",
|
|
1557
|
+
"authtoken",
|
|
1558
|
+
"credentials",
|
|
1559
|
+
"privatekey",
|
|
1560
|
+
"ssn",
|
|
1561
|
+
"creditcard",
|
|
1562
|
+
"cardnumber",
|
|
1563
|
+
"cvv",
|
|
1564
|
+
"tenantid",
|
|
1565
|
+
"tenant"
|
|
1566
|
+
]);
|
|
1567
|
+
var SENSITIVE_PAYLOAD_KEYS = new Set(SENSITIVE_FILTER_KEYS);
|
|
1568
|
+
var safeReplace = (value, seen) => {
|
|
1569
|
+
if (value === null || typeof value === "undefined") return value;
|
|
1570
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
1571
|
+
return value;
|
|
1572
|
+
}
|
|
1573
|
+
if (typeof value === "function") return "[Function]";
|
|
1574
|
+
if (typeof value === "object") {
|
|
1575
|
+
if (seen.has(value)) return "[Circular]";
|
|
1576
|
+
seen.add(value);
|
|
1577
|
+
try {
|
|
1578
|
+
if (Array.isArray(value)) {
|
|
1579
|
+
return value.map((entry) => safeReplace(entry, seen));
|
|
1580
|
+
}
|
|
1581
|
+
const out = {};
|
|
1582
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1583
|
+
out[k] = safeReplace(v, seen);
|
|
1584
|
+
}
|
|
1585
|
+
return out;
|
|
1586
|
+
} finally {
|
|
1587
|
+
seen.delete(value);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return value;
|
|
1591
|
+
};
|
|
1592
|
+
var redactObjectKeys = (value, sensitiveKeys, seen) => {
|
|
1593
|
+
if (value === null || typeof value !== "object") return value;
|
|
1594
|
+
if (seen.has(value)) return "[Circular]";
|
|
1595
|
+
seen.add(value);
|
|
1596
|
+
try {
|
|
1597
|
+
if (Array.isArray(value)) {
|
|
1598
|
+
return value.map((entry) => redactObjectKeys(entry, sensitiveKeys, seen));
|
|
1599
|
+
}
|
|
1600
|
+
const out = {};
|
|
1601
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1602
|
+
const lowered = String(k).toLowerCase();
|
|
1603
|
+
if (sensitiveKeys.has(lowered)) {
|
|
1604
|
+
out[k] = "[REDACTED]";
|
|
1605
|
+
} else {
|
|
1606
|
+
out[k] = redactObjectKeys(v, sensitiveKeys, seen);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
return out;
|
|
1610
|
+
} finally {
|
|
1611
|
+
seen.delete(value);
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
var redactFilter = (filter2) => {
|
|
1615
|
+
return redactObjectKeys(filter2, SENSITIVE_FILTER_KEYS, /* @__PURE__ */ new WeakSet());
|
|
1616
|
+
};
|
|
1617
|
+
var redactPayload = (payload) => {
|
|
1618
|
+
return redactObjectKeys(payload, SENSITIVE_PAYLOAD_KEYS, /* @__PURE__ */ new WeakSet());
|
|
1619
|
+
};
|
|
1620
|
+
var safeStringify = (value) => {
|
|
1621
|
+
try {
|
|
1622
|
+
return JSON.stringify(safeReplace(value, /* @__PURE__ */ new WeakSet()));
|
|
1623
|
+
} catch {
|
|
1624
|
+
return "[Unserializable]";
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
var countFilterKeys = (filter2) => {
|
|
1628
|
+
if (filter2 === null || typeof filter2 === "undefined") return 0;
|
|
1629
|
+
if (typeof filter2 !== "object") return 0;
|
|
1630
|
+
if (Array.isArray(filter2)) return 0;
|
|
1631
|
+
let count = 0;
|
|
1632
|
+
const stack = [filter2];
|
|
1633
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1634
|
+
while (stack.length > 0) {
|
|
1635
|
+
const current = stack.pop();
|
|
1636
|
+
if (current === null || typeof current !== "object" || Array.isArray(current)) continue;
|
|
1637
|
+
if (seen.has(current)) continue;
|
|
1638
|
+
seen.add(current);
|
|
1639
|
+
for (const [key, value] of Object.entries(current)) {
|
|
1640
|
+
count += 1;
|
|
1641
|
+
if (typeof key === "string" && key.startsWith("$") && value !== null && typeof value === "object") {
|
|
1642
|
+
if (Array.isArray(value)) {
|
|
1643
|
+
for (const entry of value) {
|
|
1644
|
+
if (entry !== null && typeof entry === "object" && !Array.isArray(entry)) {
|
|
1645
|
+
stack.push(entry);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
} else {
|
|
1649
|
+
stack.push(value);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return count;
|
|
1260
1655
|
};
|
|
1261
|
-
var
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1656
|
+
var summarizeFilter = (filter2) => {
|
|
1657
|
+
return { filterKeyValueCount: countFilterKeys(filter2) };
|
|
1658
|
+
};
|
|
1659
|
+
var resolveLogger = () => {
|
|
1660
|
+
return getGlobalOption("logger") ?? defaultLogger;
|
|
1661
|
+
};
|
|
1662
|
+
var isLevelEnabled = (level) => {
|
|
1663
|
+
const target = resolveLogger();
|
|
1664
|
+
const fn = target?.isLevelEnabled;
|
|
1665
|
+
if (typeof fn === "function") {
|
|
1666
|
+
try {
|
|
1667
|
+
return !!fn.call(target, level);
|
|
1668
|
+
} catch {
|
|
1669
|
+
return true;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
return true;
|
|
1673
|
+
};
|
|
1674
|
+
var buildOpMessage = (ctx) => {
|
|
1675
|
+
const trimmed = { op: ctx.op };
|
|
1676
|
+
if (ctx.modelName) trimmed.modelName = ctx.modelName;
|
|
1677
|
+
if (ctx.dataName) trimmed.dataName = ctx.dataName;
|
|
1678
|
+
if (ctx.page != null) trimmed.page = ctx.page;
|
|
1679
|
+
if (ctx.pageSize != null) trimmed.pageSize = ctx.pageSize;
|
|
1680
|
+
if (ctx.skip != null) trimmed.skip = ctx.skip;
|
|
1681
|
+
if (ctx.limit != null) trimmed.limit = ctx.limit;
|
|
1682
|
+
if (ctx.sort != null) trimmed.sort = ctx.sort;
|
|
1683
|
+
if (ctx.populateCount != null) trimmed.populateCount = ctx.populateCount;
|
|
1684
|
+
if (ctx.selectCount != null) trimmed.selectCount = ctx.selectCount;
|
|
1685
|
+
if (ctx.filterKeyValueCount != null) trimmed.filterKeyValueCount = ctx.filterKeyValueCount;
|
|
1686
|
+
if (ctx.startedAt != null && ctx.durationMs == null) {
|
|
1687
|
+
trimmed.startedAt = ctx.startedAt;
|
|
1688
|
+
}
|
|
1689
|
+
if (ctx.durationMs != null) trimmed.durationMs = ctx.durationMs;
|
|
1690
|
+
if (ctx.resultCode != null) trimmed.resultCode = ctx.resultCode;
|
|
1691
|
+
return safeStringify(trimmed);
|
|
1692
|
+
};
|
|
1693
|
+
var safeApply = (fn, thisArg, args) => {
|
|
1694
|
+
try {
|
|
1695
|
+
fn.apply(thisArg, args);
|
|
1696
|
+
} catch {
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
var debug = (ctx) => {
|
|
1700
|
+
if (!isLevelEnabled("debug")) return;
|
|
1701
|
+
const target = resolveLogger();
|
|
1702
|
+
const fn = target?.debug ?? defaultLogger.debug;
|
|
1703
|
+
const message = buildOpMessage(ctx);
|
|
1704
|
+
safeApply(fn, target ?? defaultLogger, [message]);
|
|
1705
|
+
};
|
|
1706
|
+
var warn = (message, meta) => {
|
|
1707
|
+
const target = resolveLogger();
|
|
1708
|
+
const fn = target?.warn ?? defaultLogger.warn;
|
|
1709
|
+
safeApply(fn, target ?? defaultLogger, meta != null ? [message, meta] : [message]);
|
|
1710
|
+
};
|
|
1711
|
+
var logInfoMessage = (message, ...args) => {
|
|
1712
|
+
const target = resolveLogger();
|
|
1713
|
+
const fn = target?.info ?? defaultLogger.info;
|
|
1714
|
+
const payload = args.length > 0 ? [message, ...args] : [message];
|
|
1715
|
+
safeApply(fn, target ?? defaultLogger, payload);
|
|
1716
|
+
};
|
|
1717
|
+
var error = (error2) => {
|
|
1718
|
+
const target = resolveLogger();
|
|
1719
|
+
const fn = target?.error ?? defaultLogger.error;
|
|
1720
|
+
safeApply(fn, target ?? defaultLogger, [error2]);
|
|
1266
1721
|
};
|
|
1267
1722
|
|
|
1268
1723
|
// src/model.ts
|
|
1724
|
+
var safeSortLogError = (msg, ...args) => {
|
|
1725
|
+
const message = args.length > 0 ? `${msg} ${safeStringify(args.length === 1 ? args[0] : args)}` : msg;
|
|
1726
|
+
error(message);
|
|
1727
|
+
};
|
|
1269
1728
|
var Model = class {
|
|
1270
|
-
constructor(modelName) {
|
|
1729
|
+
constructor(modelName, runtime) {
|
|
1271
1730
|
this.modelName = modelName;
|
|
1272
|
-
this.
|
|
1731
|
+
this.runtime = runtime ?? null;
|
|
1732
|
+
const resolvedRuntime = runtime ?? getActiveRuntime();
|
|
1733
|
+
const registered = resolvedRuntime?.getModelInstance(modelName) ?? null;
|
|
1734
|
+
const global = mongoose2.models[modelName];
|
|
1735
|
+
this.model = registered ?? global ?? mongoose2.model(modelName);
|
|
1273
1736
|
if (!this.model) return;
|
|
1274
|
-
this.model.schema.set("optimisticConcurrency", true);
|
|
1275
|
-
const currVersionKey = this.model.schema.get("versionKey");
|
|
1276
|
-
if (!currVersionKey) this.model.schema.set("versionKey", "__v");
|
|
1277
1737
|
}
|
|
1278
1738
|
new() {
|
|
1279
1739
|
const doc = new this.model();
|
|
@@ -1282,20 +1742,25 @@ var Model = class {
|
|
|
1282
1742
|
create(data) {
|
|
1283
1743
|
return this.model.create(data);
|
|
1284
1744
|
}
|
|
1285
|
-
find({ filter: filter2, select, sort, populate, limit, skip, lean }) {
|
|
1745
|
+
find({ filter: filter2, select, sort, populate, limit, hardLimit, skip, lean }) {
|
|
1286
1746
|
if (!this.validateSort(sort)) {
|
|
1287
1747
|
sort = null;
|
|
1288
1748
|
}
|
|
1289
1749
|
const builder = this.model.find(filter2);
|
|
1290
1750
|
if (select) builder.select(select);
|
|
1291
1751
|
if (skip) builder.skip(Number(skip));
|
|
1292
|
-
|
|
1752
|
+
const normalizedLimit = Number(limit);
|
|
1753
|
+
if (Number.isSafeInteger(normalizedLimit) && normalizedLimit > 0) {
|
|
1754
|
+
builder.limit(normalizedLimit);
|
|
1755
|
+
} else if (Number.isSafeInteger(hardLimit) && Number(hardLimit) > 0) {
|
|
1756
|
+
builder.limit(Number(hardLimit));
|
|
1757
|
+
}
|
|
1293
1758
|
if (sort) builder.sort(sort);
|
|
1294
1759
|
if (populate) builder.populate(populate);
|
|
1295
1760
|
if (lean) builder.lean();
|
|
1296
1761
|
return builder;
|
|
1297
1762
|
}
|
|
1298
|
-
validateSort(sort, logError =
|
|
1763
|
+
validateSort(sort, logError = safeSortLogError) {
|
|
1299
1764
|
const validSortOrders = [1, -1, "asc", "ascending", "desc", "descending"];
|
|
1300
1765
|
const fieldPathPattern = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/;
|
|
1301
1766
|
const isValidFieldPath = (field) => fieldPathPattern.test(field);
|
|
@@ -1372,7 +1837,7 @@ var Model = class {
|
|
|
1372
1837
|
}
|
|
1373
1838
|
exists(filter2) {
|
|
1374
1839
|
if (!filter2) return null;
|
|
1375
|
-
return this.findOne(filter2).select("_id").lean();
|
|
1840
|
+
return this.findOne({ filter: filter2 }).select("_id").lean();
|
|
1376
1841
|
}
|
|
1377
1842
|
// see https://mongoosejs.com/docs/api.html#query_Query-countDocuments
|
|
1378
1843
|
countDocuments(filter2 = {}) {
|
|
@@ -1389,14 +1854,16 @@ var model_default = Model;
|
|
|
1389
1854
|
import { forEach as forEach4, get as get5, pick as pick3 } from "@web-ts-toolkit/utils";
|
|
1390
1855
|
async function listSub(service, id, sub, options) {
|
|
1391
1856
|
const { filter: ft, select } = options ?? {};
|
|
1857
|
+
const filterErrors = validateClientFilter(ft);
|
|
1858
|
+
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1392
1859
|
const parentDoc = await getParentDoc(service, id, sub, null, { access: "read" });
|
|
1393
|
-
if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1860
|
+
if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
1394
1861
|
let result = get5(parentDoc, sub);
|
|
1395
1862
|
const [subFilter, subSelect] = await Promise.all([
|
|
1396
1863
|
service.genFilter(`subs.${sub}.list`, ft),
|
|
1397
1864
|
service.genQuerySelect("list", select, false, [sub, "sub"])
|
|
1398
1865
|
]);
|
|
1399
|
-
if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
|
|
1866
|
+
if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
|
|
1400
1867
|
result = filterCollection(result, subFilter);
|
|
1401
1868
|
if (subSelect) result = result.map((v) => pick3(toObject(v), subSelect.concat("_id")));
|
|
1402
1869
|
return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length };
|
|
@@ -1404,30 +1871,30 @@ async function listSub(service, id, sub, options) {
|
|
|
1404
1871
|
async function readSub(service, id, sub, subId, options) {
|
|
1405
1872
|
const { select, populate } = options ?? {};
|
|
1406
1873
|
const parentDoc = await getParentDoc(service, id, sub, { populate }, { access: "read" });
|
|
1407
|
-
if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1874
|
+
if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
1408
1875
|
const result = get5(parentDoc, sub);
|
|
1409
1876
|
const [subFilter, subSelect] = await Promise.all([
|
|
1410
1877
|
service.genFilter(`subs.${sub}.read`, { _id: subId }),
|
|
1411
1878
|
service.genQuerySelect("read", select, false, [sub, "sub"])
|
|
1412
1879
|
]);
|
|
1413
|
-
if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
|
|
1880
|
+
if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
|
|
1414
1881
|
let subdoc = findElement(result, subFilter);
|
|
1415
|
-
if (!subdoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1882
|
+
if (!subdoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
1416
1883
|
if (subSelect) subdoc = pick3(toObject(subdoc), subSelect.concat(["_id"]));
|
|
1417
1884
|
return { success: true, kind: "single", code: "success" /* Success */, data: subdoc };
|
|
1418
1885
|
}
|
|
1419
1886
|
async function updateSub(service, id, sub, subId, data) {
|
|
1420
1887
|
const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
|
|
1421
|
-
if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1888
|
+
if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
1422
1889
|
const result = get5(parentDoc, sub);
|
|
1423
1890
|
const [subFilter, subReadSelect, subUpdateSelect] = await Promise.all([
|
|
1424
1891
|
service.genFilter(`subs.${sub}.update`, { _id: subId }),
|
|
1425
1892
|
service.genQuerySelect("read", null, false, [sub, "sub"]),
|
|
1426
1893
|
service.genQuerySelect("update", null, false, [sub, "sub"])
|
|
1427
1894
|
]);
|
|
1428
|
-
if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
|
|
1895
|
+
if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
|
|
1429
1896
|
let subdoc = findElement(result, subFilter);
|
|
1430
|
-
if (!subdoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1897
|
+
if (!subdoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
1431
1898
|
const allowedData = pick3(data, subUpdateSelect);
|
|
1432
1899
|
Object.assign(subdoc, allowedData);
|
|
1433
1900
|
await parentDoc.save();
|
|
@@ -1435,15 +1902,24 @@ async function updateSub(service, id, sub, subId, data) {
|
|
|
1435
1902
|
return { success: true, kind: "single", code: "success" /* Success */, data: subdoc };
|
|
1436
1903
|
}
|
|
1437
1904
|
async function bulkUpdateSub(service, id, sub, data) {
|
|
1905
|
+
const { maxBulkItems } = service.getRequestComplexity();
|
|
1906
|
+
if (data.length > maxBulkItems) {
|
|
1907
|
+
return {
|
|
1908
|
+
success: false,
|
|
1909
|
+
kind: "error",
|
|
1910
|
+
code: "bad_request" /* BadRequest */,
|
|
1911
|
+
errors: [{ detail: `Bulk subdocument update exceeds maximum item count of ${maxBulkItems}` }]
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1438
1914
|
const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
|
|
1439
|
-
if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1915
|
+
if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
1440
1916
|
let result = get5(parentDoc, sub);
|
|
1441
1917
|
const [subFilter, subReadSelect, subUpdateSelect] = await Promise.all([
|
|
1442
1918
|
service.genFilter(`subs.${sub}.update`, { _id: { $in: data.map((v) => v._id) } }),
|
|
1443
1919
|
service.genQuerySelect("read", null, false, [sub, "sub"]),
|
|
1444
1920
|
service.genQuerySelect("update", null, false, [sub, "sub"])
|
|
1445
1921
|
]);
|
|
1446
|
-
if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
|
|
1922
|
+
if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
|
|
1447
1923
|
result = filterCollection(result, subFilter);
|
|
1448
1924
|
forEach4(result, (subdoc) => {
|
|
1449
1925
|
const tdata = findElementById(data, subdoc._id);
|
|
@@ -1457,27 +1933,40 @@ async function bulkUpdateSub(service, id, sub, data) {
|
|
|
1457
1933
|
}
|
|
1458
1934
|
async function createSub(service, id, sub, data, options) {
|
|
1459
1935
|
const { addFirst } = options ?? {};
|
|
1936
|
+
const { maxBulkItems } = service.getRequestComplexity();
|
|
1937
|
+
if (Array.isArray(data) && data.length > maxBulkItems) {
|
|
1938
|
+
return {
|
|
1939
|
+
success: false,
|
|
1940
|
+
kind: "error",
|
|
1941
|
+
code: "bad_request" /* BadRequest */,
|
|
1942
|
+
errors: [{ detail: `Bulk subdocument create exceeds maximum item count of ${maxBulkItems}` }]
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1460
1945
|
const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
|
|
1461
|
-
if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1946
|
+
if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
1462
1947
|
let result = get5(parentDoc, sub);
|
|
1463
1948
|
const [subCreateSelect, subReadSelect] = await Promise.all([
|
|
1464
1949
|
service.genQuerySelect("create", null, false, [sub, "sub"]),
|
|
1465
1950
|
service.genQuerySelect("read", null, false, [sub, "sub"])
|
|
1466
1951
|
]);
|
|
1467
|
-
const allowedData = pick3(data, subCreateSelect);
|
|
1468
|
-
|
|
1952
|
+
const allowedData = Array.isArray(data) ? data.map((row) => pick3(row, subCreateSelect)) : pick3(data, subCreateSelect);
|
|
1953
|
+
if (Array.isArray(allowedData)) {
|
|
1954
|
+
addFirst === true ? result.unshift(...allowedData) : result.push(...allowedData);
|
|
1955
|
+
} else {
|
|
1956
|
+
addFirst === true ? result.unshift(allowedData) : result.push(allowedData);
|
|
1957
|
+
}
|
|
1469
1958
|
await parentDoc.save();
|
|
1470
1959
|
if (subReadSelect) result = result.map((v) => pick3(toObject(v), subReadSelect.concat(["_id"])));
|
|
1471
1960
|
return { success: true, kind: "list", code: "created" /* Created */, data: result, count: result.length };
|
|
1472
1961
|
}
|
|
1473
1962
|
async function deleteSub(service, id, sub, subId) {
|
|
1474
1963
|
const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
|
|
1475
|
-
if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1964
|
+
if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
1476
1965
|
const result = get5(parentDoc, sub);
|
|
1477
1966
|
const subFilter = await service.genFilter(`subs.${sub}.delete`, { _id: subId });
|
|
1478
|
-
if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
|
|
1967
|
+
if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
|
|
1479
1968
|
const subdoc = findElement(result, subFilter);
|
|
1480
|
-
if (!subdoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1969
|
+
if (!subdoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
1481
1970
|
await ("deleteOne" in subdoc ? subdoc.deleteOne?.() : subdoc.remove?.());
|
|
1482
1971
|
await parentDoc.save();
|
|
1483
1972
|
return { success: true, kind: "single", code: "success" /* Success */, data: subdoc._id };
|
|
@@ -1487,7 +1976,7 @@ async function getParentDoc(service, id, sub, args, options) {
|
|
|
1487
1976
|
const { access = "read", lean = false } = options ?? {};
|
|
1488
1977
|
const parentFilter = await service.genFilter(access, await service.genIDFilter(id));
|
|
1489
1978
|
if (parentFilter === false) return null;
|
|
1490
|
-
return service.
|
|
1979
|
+
return service.findRawParentDoc({ filter: parentFilter, select: sub, populate: genSubPopulate(sub, populate), lean });
|
|
1491
1980
|
}
|
|
1492
1981
|
|
|
1493
1982
|
// src/services/model-service-defaults.ts
|
|
@@ -1613,10 +2102,53 @@ var assertModelDocument = (value, modelName, hookName) => {
|
|
|
1613
2102
|
}
|
|
1614
2103
|
throw new Error(`${hookName} hook for model=${modelName} must return a Mongoose document instance`);
|
|
1615
2104
|
};
|
|
2105
|
+
var mapWithConcurrencyLimit = async (items, limit, iteratee) => {
|
|
2106
|
+
const results = new Array(items.length);
|
|
2107
|
+
let cursor = 0;
|
|
2108
|
+
const workerCount = Math.min(Math.max(limit, 1), items.length || 1);
|
|
2109
|
+
await Promise.all(
|
|
2110
|
+
Array.from({ length: workerCount }, async () => {
|
|
2111
|
+
while (cursor < items.length) {
|
|
2112
|
+
const current = cursor;
|
|
2113
|
+
cursor += 1;
|
|
2114
|
+
results[current] = await iteratee(items[current], current);
|
|
2115
|
+
}
|
|
2116
|
+
})
|
|
2117
|
+
);
|
|
2118
|
+
return results;
|
|
2119
|
+
};
|
|
1616
2120
|
var Service = class extends Base {
|
|
2121
|
+
findRawParentDoc(args) {
|
|
2122
|
+
return this.model.findOne({
|
|
2123
|
+
...args,
|
|
2124
|
+
filter: args.filter,
|
|
2125
|
+
populate: args.populate
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
1617
2128
|
asServiceHookContext(context) {
|
|
1618
2129
|
return context;
|
|
1619
2130
|
}
|
|
2131
|
+
beginOp(op, filter2, extra) {
|
|
2132
|
+
const startedAt = Date.now();
|
|
2133
|
+
debug({
|
|
2134
|
+
op,
|
|
2135
|
+
modelName: this.modelName,
|
|
2136
|
+
filterKeyValueCount: summarizeFilter(filter2).filterKeyValueCount,
|
|
2137
|
+
startedAt,
|
|
2138
|
+
...extra ?? {}
|
|
2139
|
+
});
|
|
2140
|
+
return startedAt;
|
|
2141
|
+
}
|
|
2142
|
+
completeOp(op, startedAt, resultCode, filter2, extra) {
|
|
2143
|
+
debug({
|
|
2144
|
+
op,
|
|
2145
|
+
modelName: this.modelName,
|
|
2146
|
+
filterKeyValueCount: summarizeFilter(filter2).filterKeyValueCount,
|
|
2147
|
+
durationMs: Date.now() - startedAt,
|
|
2148
|
+
resultCode,
|
|
2149
|
+
...extra ?? {}
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
1620
2152
|
constructor(req, modelName) {
|
|
1621
2153
|
super(req, modelName);
|
|
1622
2154
|
this.model = new model_default(modelName);
|
|
@@ -1627,12 +2159,20 @@ var Service = class extends Base {
|
|
|
1627
2159
|
}
|
|
1628
2160
|
async findOne(filter2, args, options) {
|
|
1629
2161
|
const filterErrors = this.validateClientFilter(filter2);
|
|
1630
|
-
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
2162
|
+
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1631
2163
|
const { select, sort, populate, include, overrides } = this.resolveFindOneArgs(args);
|
|
1632
2164
|
const { skim, includePermissions, access, populateAccess, lean } = this.resolveFindOneOptions(options);
|
|
1633
2165
|
const { filter: overrideFilter, select: overrideSelect, populate: overridePopulate } = overrides ?? {};
|
|
2166
|
+
let parsedFilter;
|
|
2167
|
+
try {
|
|
2168
|
+
parsedFilter = await this.parseClientData(filter2);
|
|
2169
|
+
} catch (error2) {
|
|
2170
|
+
const result = this.getClientRequestErrorResult(error2);
|
|
2171
|
+
if (result) return result;
|
|
2172
|
+
throw error2;
|
|
2173
|
+
}
|
|
1634
2174
|
let [_filter, _select, _populate] = await Promise.all([
|
|
1635
|
-
overrideFilter || this.genFilter(access,
|
|
2175
|
+
overrideFilter || this.genFilter(access, parsedFilter),
|
|
1636
2176
|
overrideSelect || this.genQuerySelect(access, select),
|
|
1637
2177
|
overridePopulate || this.genPopulate(populateAccess || access, populate)
|
|
1638
2178
|
]);
|
|
@@ -1644,10 +2184,20 @@ var Service = class extends Base {
|
|
|
1644
2184
|
sort,
|
|
1645
2185
|
populate: _populate
|
|
1646
2186
|
};
|
|
1647
|
-
|
|
1648
|
-
|
|
2187
|
+
const startedAt = this.beginOp("findOne", _filter, {
|
|
2188
|
+
sort,
|
|
2189
|
+
selectCount: finalSelect.length,
|
|
2190
|
+
populateCount: Array.isArray(_populate) ? _populate.length : _populate ? 1 : 0
|
|
2191
|
+
});
|
|
2192
|
+
if (_filter === false) {
|
|
2193
|
+
this.completeOp("findOne", startedAt, "forbidden" /* Forbidden */, _filter);
|
|
2194
|
+
return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
2195
|
+
}
|
|
1649
2196
|
let doc = await this.model.findOne({ ...query, lean });
|
|
1650
|
-
if (!doc)
|
|
2197
|
+
if (!doc) {
|
|
2198
|
+
this.completeOp("findOne", startedAt, "not_found" /* NotFound */, _filter);
|
|
2199
|
+
return { success: false, kind: "error", code: "not_found" /* NotFound */, query };
|
|
2200
|
+
}
|
|
1651
2201
|
const context = {
|
|
1652
2202
|
mongooseModel: this.model.model,
|
|
1653
2203
|
modelName: this.modelName,
|
|
@@ -1655,7 +2205,16 @@ var Service = class extends Base {
|
|
|
1655
2205
|
originalDocumentSnapshot: toObject(doc),
|
|
1656
2206
|
resolvedQuery: query
|
|
1657
2207
|
};
|
|
1658
|
-
|
|
2208
|
+
try {
|
|
2209
|
+
doc = await this.includeDocs(doc, includes);
|
|
2210
|
+
} catch (error2) {
|
|
2211
|
+
const result = this.getClientRequestErrorResult(error2);
|
|
2212
|
+
if (result) {
|
|
2213
|
+
this.completeOp("findOne", startedAt, result.code, _filter);
|
|
2214
|
+
return { ...result, query };
|
|
2215
|
+
}
|
|
2216
|
+
throw error2;
|
|
2217
|
+
}
|
|
1659
2218
|
let includeDocPermissions = includePermissions;
|
|
1660
2219
|
if (!includeDocPermissions && !skim) {
|
|
1661
2220
|
includeDocPermissions = this.checkIfModelPermissionExists([access, "read", "update"]);
|
|
@@ -1668,6 +2227,7 @@ var Service = class extends Base {
|
|
|
1668
2227
|
this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
|
|
1669
2228
|
);
|
|
1670
2229
|
if (!includePermissions) doc = this.addEmptyPermissions(doc);
|
|
2230
|
+
this.completeOp("findOne", startedAt, "success" /* Success */, _filter);
|
|
1671
2231
|
return { success: true, kind: "single", code: "success" /* Success */, data: doc, query, context };
|
|
1672
2232
|
}
|
|
1673
2233
|
async findById(id, args, options) {
|
|
@@ -1691,18 +2251,26 @@ var Service = class extends Base {
|
|
|
1691
2251
|
}
|
|
1692
2252
|
async find(filter2, args, options, decorate) {
|
|
1693
2253
|
const filterErrors = this.validateClientFilter(filter2);
|
|
1694
|
-
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
2254
|
+
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1695
2255
|
const { select, populate, include, sort, skip, limit, page, pageSize, overrides } = this.resolveFindArgs(args);
|
|
1696
2256
|
const { skim, includePermissions, includeCount, populateAccess, lean } = this.resolveFindOptions(options);
|
|
1697
2257
|
const { filter: overrideFilter, select: overrideSelect, populate: overridePopulate } = overrides ?? {};
|
|
2258
|
+
let parsedFilter;
|
|
2259
|
+
try {
|
|
2260
|
+
parsedFilter = await this.parseClientData(filter2);
|
|
2261
|
+
} catch (error2) {
|
|
2262
|
+
const result = this.getClientRequestErrorResult(error2);
|
|
2263
|
+
if (result) return result;
|
|
2264
|
+
throw error2;
|
|
2265
|
+
}
|
|
1698
2266
|
const [_filter, _select, _populate, pagination] = await Promise.all([
|
|
1699
|
-
overrideFilter || this.genFilter("list",
|
|
2267
|
+
overrideFilter || this.genFilter("list", parsedFilter),
|
|
1700
2268
|
overrideSelect || this.genQuerySelect("list", select),
|
|
1701
2269
|
overridePopulate || this.genPopulate(populateAccess, populate),
|
|
1702
2270
|
genPagination({ skip, limit, page, pageSize }, this.options.listHardLimit)
|
|
1703
2271
|
]);
|
|
1704
2272
|
const finalSelect = normalizeSelect(_select);
|
|
1705
|
-
const filteredPopulate =
|
|
2273
|
+
const filteredPopulate = isArray7(finalSelect) && isArray7(_populate) ? _populate.filter((p) => finalSelect.includes(p.path.split(".")[0])) : _populate;
|
|
1706
2274
|
const { includes, includeLocalFields, includePaths } = this.processInclude(include);
|
|
1707
2275
|
const query = {
|
|
1708
2276
|
filter: _filter,
|
|
@@ -1711,10 +2279,20 @@ var Service = class extends Base {
|
|
|
1711
2279
|
sort,
|
|
1712
2280
|
...pagination
|
|
1713
2281
|
};
|
|
1714
|
-
|
|
1715
|
-
|
|
2282
|
+
const startedAt = this.beginOp("find", _filter, {
|
|
2283
|
+
sort,
|
|
2284
|
+
skip: pagination.skip,
|
|
2285
|
+
limit: pagination.limit,
|
|
2286
|
+
selectCount: finalSelect.concat(includeLocalFields).length,
|
|
2287
|
+
populateCount: Array.isArray(filteredPopulate) ? filteredPopulate.length : filteredPopulate ? 1 : 0
|
|
2288
|
+
});
|
|
2289
|
+
if (_filter === false) {
|
|
2290
|
+
this.completeOp("find", startedAt, "forbidden" /* Forbidden */, _filter);
|
|
2291
|
+
return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
2292
|
+
}
|
|
1716
2293
|
let docs = await this.model.find({
|
|
1717
2294
|
...query,
|
|
2295
|
+
hardLimit: this.options.listHardLimit,
|
|
1718
2296
|
lean
|
|
1719
2297
|
});
|
|
1720
2298
|
const contexts = docs.map((doc) => ({
|
|
@@ -1725,7 +2303,16 @@ var Service = class extends Base {
|
|
|
1725
2303
|
resolvedQuery: query
|
|
1726
2304
|
}));
|
|
1727
2305
|
const _decorate = isFunction3(decorate) ? decorate : (v) => v;
|
|
1728
|
-
|
|
2306
|
+
try {
|
|
2307
|
+
docs = await this.includeDocs(docs, includes);
|
|
2308
|
+
} catch (error2) {
|
|
2309
|
+
const result = this.getClientRequestErrorResult(error2);
|
|
2310
|
+
if (result) {
|
|
2311
|
+
this.completeOp("find", startedAt, result.code, _filter);
|
|
2312
|
+
return { ...result, query };
|
|
2313
|
+
}
|
|
2314
|
+
throw error2;
|
|
2315
|
+
}
|
|
1729
2316
|
const fieldPermissionAccess = includePermissions ? await this.getFieldPermissionAccess(docs.map((doc) => doc._id)) : void 0;
|
|
1730
2317
|
docs = await Promise.all(
|
|
1731
2318
|
docs.map(async (doc, i) => {
|
|
@@ -1746,6 +2333,7 @@ var Service = class extends Base {
|
|
|
1746
2333
|
return doc;
|
|
1747
2334
|
})
|
|
1748
2335
|
);
|
|
2336
|
+
this.completeOp("find", startedAt, "success" /* Success */, _filter);
|
|
1749
2337
|
return {
|
|
1750
2338
|
success: true,
|
|
1751
2339
|
kind: "list",
|
|
@@ -1762,67 +2350,96 @@ var Service = class extends Base {
|
|
|
1762
2350
|
const { skim, includePermissions, populateAccess } = this.resolveCreateOptions(options);
|
|
1763
2351
|
const isArr = Array.isArray(data);
|
|
1764
2352
|
let dataArr = isArr ? data : [data];
|
|
1765
|
-
|
|
2353
|
+
const { maxBulkItems, maxBulkConcurrency } = this.getRequestComplexity();
|
|
2354
|
+
if (dataArr.length > maxBulkItems) {
|
|
2355
|
+
return {
|
|
2356
|
+
success: false,
|
|
2357
|
+
kind: "error",
|
|
2358
|
+
code: "bad_request" /* BadRequest */,
|
|
2359
|
+
errors: [{ detail: `Bulk create exceeds maximum item count of ${maxBulkItems}` }]
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
try {
|
|
2363
|
+
dataArr = await Promise.all(dataArr.map((d) => this.parseClientData(d)));
|
|
2364
|
+
} catch (error2) {
|
|
2365
|
+
const result = this.getClientRequestErrorResult(error2);
|
|
2366
|
+
if (result) return result;
|
|
2367
|
+
throw error2;
|
|
2368
|
+
}
|
|
1766
2369
|
const resolvedPopulate = populate ? await this.genPopulate(populateAccess, populate) : [];
|
|
1767
2370
|
const contexts = [];
|
|
1768
|
-
|
|
1769
|
-
const
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
if (
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
return;
|
|
1787
|
-
}
|
|
1788
|
-
} else if (isArray6(validated)) {
|
|
1789
|
-
if (validated.length > 0) {
|
|
1790
|
-
validationError = { success: false, code: "bad_request" /* BadRequest */, errors: validated };
|
|
1791
|
-
return;
|
|
1792
|
-
}
|
|
2371
|
+
const validationErrors = [];
|
|
2372
|
+
const validationItems = await mapWithConcurrencyLimit(dataArr, maxBulkConcurrency, async (item, index) => {
|
|
2373
|
+
const context = {
|
|
2374
|
+
mongooseModel: this.model.model,
|
|
2375
|
+
modelName: this.modelName,
|
|
2376
|
+
operation: "create",
|
|
2377
|
+
originalData: item,
|
|
2378
|
+
resolvedQuery: resolvedPopulate.length > 0 ? { populate: resolvedPopulate } : {}
|
|
2379
|
+
};
|
|
2380
|
+
const allowedFields = await this.genAllowedFields(item, "create");
|
|
2381
|
+
const allowedData = pick4(item, allowedFields);
|
|
2382
|
+
context.allowedFields = allowedFields;
|
|
2383
|
+
context.allowedData = allowedData;
|
|
2384
|
+
const validated = await this.validate(allowedData, "create", context);
|
|
2385
|
+
if (isBoolean3(validated)) {
|
|
2386
|
+
if (!validated) {
|
|
2387
|
+
validationErrors.push({ index, errors: [] });
|
|
2388
|
+
return void 0;
|
|
1793
2389
|
}
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
2390
|
+
} else if (isArray7(validated)) {
|
|
2391
|
+
if (validated.length > 0) {
|
|
2392
|
+
validationErrors.push({
|
|
2393
|
+
index,
|
|
2394
|
+
errors: isArr ? validated.map((issue) => this.formatBulkValidationIssue(issue, index)) : validated
|
|
2395
|
+
});
|
|
2396
|
+
return void 0;
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
contexts[index] = context;
|
|
2400
|
+
return allowedData;
|
|
2401
|
+
});
|
|
2402
|
+
if (validationErrors.length > 0) {
|
|
2403
|
+
const aggregate = validationErrors.slice().sort((a, b) => a.index - b.index).flatMap((entry) => entry.errors);
|
|
2404
|
+
if (isArr) {
|
|
2405
|
+
return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: aggregate };
|
|
2406
|
+
}
|
|
2407
|
+
const single = validationErrors[0];
|
|
2408
|
+
return {
|
|
2409
|
+
success: false,
|
|
2410
|
+
kind: "error",
|
|
2411
|
+
code: "bad_request" /* BadRequest */,
|
|
2412
|
+
errors: single?.errors ?? []
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
const items = await mapWithConcurrencyLimit(validationItems, maxBulkConcurrency, async (allowedData, index) => {
|
|
2416
|
+
const preparedData = await this.prepare(allowedData, "create", contexts[index]);
|
|
2417
|
+
contexts[index].preparedData = preparedData;
|
|
2418
|
+
return preparedData;
|
|
2419
|
+
});
|
|
1801
2420
|
const _decorate = isFunction3(decorate) ? decorate : (v) => v;
|
|
1802
2421
|
const createdDocs = await this.model.create(items);
|
|
1803
|
-
const docs = await
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
doc
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
})
|
|
1825
|
-
);
|
|
2422
|
+
const docs = await mapWithConcurrencyLimit(createdDocs, maxBulkConcurrency, async (doc, index) => {
|
|
2423
|
+
contexts[index].currentDocument = doc;
|
|
2424
|
+
doc = assertModelDocument(
|
|
2425
|
+
await this.afterPersist(doc, "create", contexts[index]),
|
|
2426
|
+
this.modelName,
|
|
2427
|
+
"afterPersist"
|
|
2428
|
+
);
|
|
2429
|
+
contexts[index].currentDocument = doc;
|
|
2430
|
+
contexts[index].finalDocumentSnapshot = doc.toObject({ virtuals: false });
|
|
2431
|
+
let includeDocPermissions = includePermissions;
|
|
2432
|
+
if (!includeDocPermissions && !skim) {
|
|
2433
|
+
includeDocPermissions = this.checkIfModelPermissionExists(["create", "read", "update"]);
|
|
2434
|
+
}
|
|
2435
|
+
if (includeDocPermissions) doc = await this.addDocPermissions(doc, "create", contexts[index]);
|
|
2436
|
+
if (includePermissions) doc = await this.addFieldPermissions(doc, "read", contexts[index]);
|
|
2437
|
+
if (resolvedPopulate.length > 0) await populateDoc(doc, resolvedPopulate);
|
|
2438
|
+
doc = await this.trimOutputFields(doc, "read", this.baseFieldsExt);
|
|
2439
|
+
let outputDoc = await _decorate(doc, contexts[index]);
|
|
2440
|
+
if (!includePermissions) outputDoc = this.addEmptyPermissions(outputDoc);
|
|
2441
|
+
return outputDoc;
|
|
2442
|
+
});
|
|
1826
2443
|
return {
|
|
1827
2444
|
success: true,
|
|
1828
2445
|
kind: "list",
|
|
@@ -1832,18 +2449,50 @@ var Service = class extends Base {
|
|
|
1832
2449
|
count: docs.length
|
|
1833
2450
|
};
|
|
1834
2451
|
}
|
|
1835
|
-
|
|
2452
|
+
formatBulkValidationIssue(issue, index) {
|
|
2453
|
+
if (!issue || typeof issue !== "object") {
|
|
2454
|
+
return {
|
|
2455
|
+
detail: typeof issue === "string" && issue.length > 0 ? issue : "Bad Request",
|
|
2456
|
+
...index === null ? {} : { pointer: `#/${index}` }
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
const typedIssue = issue;
|
|
2460
|
+
const detail = typedIssue.detail ?? typedIssue.message ?? "Bad Request";
|
|
2461
|
+
if (index === null) {
|
|
2462
|
+
return typedIssue.pointer || typedIssue.path ? {
|
|
2463
|
+
detail,
|
|
2464
|
+
...typedIssue.pointer ? { pointer: typedIssue.pointer } : {}
|
|
2465
|
+
} : { detail };
|
|
2466
|
+
}
|
|
2467
|
+
if (typedIssue.pointer?.startsWith("#/")) {
|
|
2468
|
+
return { ...typedIssue, detail, pointer: `#/${index}${typedIssue.pointer.slice(1)}` };
|
|
2469
|
+
}
|
|
2470
|
+
if (typedIssue.path) {
|
|
2471
|
+
return { ...typedIssue, detail, pointer: `#/${[index, ...typedIssue.path].join("/")}` };
|
|
2472
|
+
}
|
|
2473
|
+
return { ...typedIssue, detail, pointer: `#/${index}` };
|
|
2474
|
+
}
|
|
2475
|
+
async new(args, options) {
|
|
2476
|
+
const { skim, includePermissions } = options ?? {};
|
|
1836
2477
|
const data = await this.model.new();
|
|
2478
|
+
let doc = data;
|
|
2479
|
+
doc = await this.trimOutputFields(doc, "create", this.baseFieldsExt);
|
|
2480
|
+
let includeDocPermissions = includePermissions;
|
|
2481
|
+
if (!includeDocPermissions && !skim) {
|
|
2482
|
+
includeDocPermissions = this.checkIfModelPermissionExists(["create", "read", "update"]);
|
|
2483
|
+
}
|
|
2484
|
+
if (includeDocPermissions) doc = await this.addDocPermissions(doc, "create", {});
|
|
2485
|
+
if (!includePermissions) doc = this.addEmptyPermissions(doc);
|
|
1837
2486
|
return {
|
|
1838
2487
|
success: true,
|
|
1839
2488
|
kind: "single",
|
|
1840
2489
|
code: "success" /* Success */,
|
|
1841
|
-
data
|
|
2490
|
+
data: doc
|
|
1842
2491
|
};
|
|
1843
2492
|
}
|
|
1844
2493
|
async updateOne(filter2, data, args, options, decorate) {
|
|
1845
2494
|
const filterErrors = this.validateClientFilter(filter2);
|
|
1846
|
-
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
2495
|
+
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1847
2496
|
const { populate, overrides } = this.resolveUpdateOneArgs(args);
|
|
1848
2497
|
const { skim, includePermissions, populateAccess } = this.resolveUpdateOneOptions(options);
|
|
1849
2498
|
const { filter: overrideFilter, populate: overridePopulate } = overrides ?? {};
|
|
@@ -1852,17 +2501,34 @@ var Service = class extends Base {
|
|
|
1852
2501
|
overridePopulate || this.genPopulate(populateAccess, populate)
|
|
1853
2502
|
]);
|
|
1854
2503
|
const query = { filter: _filter, populate: _populate };
|
|
1855
|
-
|
|
1856
|
-
|
|
2504
|
+
const startedAt = this.beginOp("updateOne", _filter, {
|
|
2505
|
+
populateCount: Array.isArray(_populate) ? _populate.length : _populate ? 1 : 0
|
|
2506
|
+
});
|
|
2507
|
+
if (_filter === false) {
|
|
2508
|
+
this.completeOp("updateOne", startedAt, "forbidden" /* Forbidden */, _filter);
|
|
2509
|
+
return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
2510
|
+
}
|
|
1857
2511
|
let doc = await this.model.findOne({ filter: _filter });
|
|
1858
|
-
if (!doc)
|
|
2512
|
+
if (!doc) {
|
|
2513
|
+
this.completeOp("updateOne", startedAt, "not_found" /* NotFound */, _filter);
|
|
2514
|
+
return { success: false, kind: "error", code: "not_found" /* NotFound */, query };
|
|
2515
|
+
}
|
|
1859
2516
|
const context = {
|
|
1860
2517
|
mongooseModel: this.model.model,
|
|
1861
2518
|
modelName: this.modelName,
|
|
1862
2519
|
operation: "update",
|
|
1863
2520
|
resolvedQuery: query
|
|
1864
2521
|
};
|
|
1865
|
-
|
|
2522
|
+
try {
|
|
2523
|
+
data = await this.parseClientData(data);
|
|
2524
|
+
} catch (error2) {
|
|
2525
|
+
const result = this.getClientRequestErrorResult(error2);
|
|
2526
|
+
if (result) {
|
|
2527
|
+
this.completeOp("updateOne", startedAt, result.code, _filter);
|
|
2528
|
+
return result;
|
|
2529
|
+
}
|
|
2530
|
+
throw error2;
|
|
2531
|
+
}
|
|
1866
2532
|
context.originalDocumentSnapshot = doc.toObject({ virtuals: false });
|
|
1867
2533
|
context.originalData = data;
|
|
1868
2534
|
doc = await this.addDocPermissions(doc, "update", context);
|
|
@@ -1874,9 +2540,15 @@ var Service = class extends Base {
|
|
|
1874
2540
|
context.allowedData = allowedData;
|
|
1875
2541
|
const validated = await this.validate(allowedData, "update", context);
|
|
1876
2542
|
if (isBoolean3(validated)) {
|
|
1877
|
-
if (!validated)
|
|
1878
|
-
|
|
1879
|
-
|
|
2543
|
+
if (!validated) {
|
|
2544
|
+
this.completeOp("updateOne", startedAt, "bad_request" /* BadRequest */, _filter);
|
|
2545
|
+
return { success: false, kind: "error", code: "bad_request" /* BadRequest */ };
|
|
2546
|
+
}
|
|
2547
|
+
} else if (isArray7(validated)) {
|
|
2548
|
+
if (validated.length > 0) {
|
|
2549
|
+
this.completeOp("updateOne", startedAt, "bad_request" /* BadRequest */, _filter);
|
|
2550
|
+
return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: validated };
|
|
2551
|
+
}
|
|
1880
2552
|
}
|
|
1881
2553
|
const prepared = await this.prepare(allowedData, "update", context);
|
|
1882
2554
|
context.preparedData = prepared;
|
|
@@ -1909,6 +2581,7 @@ var Service = class extends Base {
|
|
|
1909
2581
|
let outputDoc = doc;
|
|
1910
2582
|
if (isFunction3(decorate)) outputDoc = await decorate(outputDoc, context);
|
|
1911
2583
|
if (!includePermissions) outputDoc = this.addEmptyPermissions(outputDoc);
|
|
2584
|
+
this.completeOp("updateOne", startedAt, "success" /* Success */, _filter);
|
|
1912
2585
|
return { success: true, kind: "single", code: "success" /* Success */, data: outputDoc, input: prepared };
|
|
1913
2586
|
}
|
|
1914
2587
|
async updateById(id, data, args = {}, options = {}, decorate) {
|
|
@@ -1931,17 +2604,21 @@ var Service = class extends Base {
|
|
|
1931
2604
|
}
|
|
1932
2605
|
async upsert(filter2, data, args, options, decorate) {
|
|
1933
2606
|
const filterErrors = this.validateClientFilter(filter2);
|
|
1934
|
-
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
2607
|
+
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1935
2608
|
const { populate, overrides } = this.resolveUpsertArgs(args);
|
|
1936
2609
|
const { skim, includePermissions, populateAccess } = this.resolveUpsertOptions(options);
|
|
1937
2610
|
const { filter: overrideFilter, populate: overridePopulate } = overrides ?? {};
|
|
1938
2611
|
const _filter = await (overrideFilter || this.genFilter("update", filter2));
|
|
1939
2612
|
const query = { filter: _filter };
|
|
1940
|
-
|
|
1941
|
-
if (_filter === false)
|
|
2613
|
+
const startedAt = this.beginOp("upsert", _filter);
|
|
2614
|
+
if (_filter === false) {
|
|
2615
|
+
this.completeOp("upsert", startedAt, "forbidden" /* Forbidden */, _filter);
|
|
2616
|
+
return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
2617
|
+
}
|
|
1942
2618
|
const theone = await this.model.findOne({ filter: _filter });
|
|
2619
|
+
let result;
|
|
1943
2620
|
if (theone) {
|
|
1944
|
-
|
|
2621
|
+
result = await this.updateOne(
|
|
1945
2622
|
null,
|
|
1946
2623
|
data,
|
|
1947
2624
|
{
|
|
@@ -1955,7 +2632,7 @@ var Service = class extends Base {
|
|
|
1955
2632
|
decorate
|
|
1956
2633
|
);
|
|
1957
2634
|
} else {
|
|
1958
|
-
|
|
2635
|
+
result = await this.create(
|
|
1959
2636
|
data,
|
|
1960
2637
|
{ populate },
|
|
1961
2638
|
{
|
|
@@ -1966,14 +2643,22 @@ var Service = class extends Base {
|
|
|
1966
2643
|
decorate
|
|
1967
2644
|
);
|
|
1968
2645
|
}
|
|
2646
|
+
this.completeOp("upsert", startedAt, result.code, _filter);
|
|
2647
|
+
return result;
|
|
1969
2648
|
}
|
|
1970
2649
|
async delete(id) {
|
|
1971
2650
|
const filter2 = await this.genFilter("delete", await this.genIDFilter(id));
|
|
1972
2651
|
const query = { filter: filter2 };
|
|
1973
|
-
|
|
1974
|
-
if (filter2 === false)
|
|
2652
|
+
const startedAt = this.beginOp("delete", filter2);
|
|
2653
|
+
if (filter2 === false) {
|
|
2654
|
+
this.completeOp("delete", startedAt, "forbidden" /* Forbidden */, filter2);
|
|
2655
|
+
return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
2656
|
+
}
|
|
1975
2657
|
let doc = await this.model.findOne({ filter: filter2 });
|
|
1976
|
-
if (!doc)
|
|
2658
|
+
if (!doc) {
|
|
2659
|
+
this.completeOp("delete", startedAt, "not_found" /* NotFound */, filter2);
|
|
2660
|
+
return { success: false, kind: "error", code: "not_found" /* NotFound */, query };
|
|
2661
|
+
}
|
|
1977
2662
|
const context = {
|
|
1978
2663
|
mongooseModel: this.model.model,
|
|
1979
2664
|
modelName: this.modelName,
|
|
@@ -1986,11 +2671,12 @@ var Service = class extends Base {
|
|
|
1986
2671
|
await ("deleteOne" in doc ? doc.deleteOne() : doc.remove());
|
|
1987
2672
|
context.finalDocumentSnapshot = toObject(doc);
|
|
1988
2673
|
await this.afterDelete(doc, context);
|
|
2674
|
+
this.completeOp("delete", startedAt, "success" /* Success */, filter2);
|
|
1989
2675
|
return { success: true, kind: "single", code: "success" /* Success */, data: doc._id, query };
|
|
1990
2676
|
}
|
|
1991
2677
|
async exists(filter2, options) {
|
|
1992
2678
|
const filterErrors = this.validateClientFilter(filter2);
|
|
1993
|
-
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
2679
|
+
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1994
2680
|
const { access, includeId } = this.resolveExistsOptions(options);
|
|
1995
2681
|
filter2 = await this.genFilter(access, filter2);
|
|
1996
2682
|
const result = await this.model.exists(filter2);
|
|
@@ -2002,22 +2688,58 @@ var Service = class extends Base {
|
|
|
2002
2688
|
query: { filter: filter2 }
|
|
2003
2689
|
};
|
|
2004
2690
|
}
|
|
2691
|
+
isValidDistinctFieldName(field) {
|
|
2692
|
+
if (typeof field !== "string" || field.length === 0) return false;
|
|
2693
|
+
if (field.includes("$")) return false;
|
|
2694
|
+
if (field.includes("..")) return false;
|
|
2695
|
+
if (field.startsWith(".") || field.endsWith(".")) return false;
|
|
2696
|
+
if (/\s/.test(field)) return false;
|
|
2697
|
+
return true;
|
|
2698
|
+
}
|
|
2699
|
+
async authorizeDistinctField(field) {
|
|
2700
|
+
const allowedFields = await this.genAllowedFields(null, "read");
|
|
2701
|
+
const isAllowed = allowedFields.some((allowed) => {
|
|
2702
|
+
if (allowed === field) return true;
|
|
2703
|
+
if (allowed.startsWith(`${field}.`)) return true;
|
|
2704
|
+
if (field.startsWith(`${allowed}.`)) return true;
|
|
2705
|
+
return false;
|
|
2706
|
+
});
|
|
2707
|
+
if (!isAllowed) {
|
|
2708
|
+
return {
|
|
2709
|
+
success: false,
|
|
2710
|
+
kind: "error",
|
|
2711
|
+
code: "forbidden" /* Forbidden */,
|
|
2712
|
+
errors: [{ detail: `Distinct field not allowed: ${field}` }]
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
return null;
|
|
2716
|
+
}
|
|
2005
2717
|
async distinct(field, args) {
|
|
2718
|
+
if (!this.isValidDistinctFieldName(field)) {
|
|
2719
|
+
return {
|
|
2720
|
+
success: false,
|
|
2721
|
+
kind: "error",
|
|
2722
|
+
code: "bad_request" /* BadRequest */,
|
|
2723
|
+
errors: [{ detail: `Invalid distinct field: ${field}` }]
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
const fieldError = await this.authorizeDistinctField(field);
|
|
2727
|
+
if (fieldError) return fieldError;
|
|
2006
2728
|
let { filter: filter2 } = args ?? {};
|
|
2007
2729
|
const filterErrors = this.validateClientFilter(filter2);
|
|
2008
|
-
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
2730
|
+
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
2009
2731
|
filter2 = await this.genFilter("read", filter2);
|
|
2010
2732
|
const query = { filter: filter2 };
|
|
2011
|
-
if (filter2 === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
2733
|
+
if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
2012
2734
|
const result = await this.model.distinct(field, filter2);
|
|
2013
2735
|
return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length, query };
|
|
2014
2736
|
}
|
|
2015
2737
|
async count(filter2, access = "list") {
|
|
2016
2738
|
const filterErrors = this.validateClientFilter(filter2);
|
|
2017
|
-
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
2739
|
+
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
2018
2740
|
filter2 = await this.genFilter(access, filter2);
|
|
2019
2741
|
const query = { filter: filter2 };
|
|
2020
|
-
if (filter2 === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
2742
|
+
if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
2021
2743
|
return { success: true, kind: "single", code: "success" /* Success */, data: await this.model.countDocuments(filter2), query };
|
|
2022
2744
|
}
|
|
2023
2745
|
getDocPermissions(doc) {
|
|
@@ -2149,11 +2871,11 @@ var PublicService = class extends Service {
|
|
|
2149
2871
|
{ populate },
|
|
2150
2872
|
{ skim, includePermissions, populateAccess },
|
|
2151
2873
|
async (doc, context) => {
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
if (select)
|
|
2156
|
-
return
|
|
2874
|
+
let d = toObject(doc);
|
|
2875
|
+
d = await this.decorate(d, "create", context);
|
|
2876
|
+
d = this.runTasks(d, tasks);
|
|
2877
|
+
if (select) d = pick5(d, [...normalizeSelect(select), ...this.baseFieldsExt]);
|
|
2878
|
+
return d;
|
|
2157
2879
|
}
|
|
2158
2880
|
);
|
|
2159
2881
|
if (!result.success) {
|
|
@@ -2161,8 +2883,8 @@ var PublicService = class extends Service {
|
|
|
2161
2883
|
}
|
|
2162
2884
|
return result;
|
|
2163
2885
|
}
|
|
2164
|
-
async _new() {
|
|
2165
|
-
return this.new();
|
|
2886
|
+
async _new(args, options) {
|
|
2887
|
+
return this.new(args, options);
|
|
2166
2888
|
}
|
|
2167
2889
|
async _read(id, args, options) {
|
|
2168
2890
|
const { select, populate, include, tasks } = this.resolvePublicReadArgs(args);
|
|
@@ -2244,12 +2966,12 @@ var PublicService = class extends Service {
|
|
|
2244
2966
|
{ populate },
|
|
2245
2967
|
{ skim, includePermissions, populateAccess },
|
|
2246
2968
|
async (doc, context) => {
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
if (select)
|
|
2251
|
-
else if (!returningAll)
|
|
2252
|
-
return
|
|
2969
|
+
let d = toObject(doc);
|
|
2970
|
+
d = await this.decorate(d, "update", context);
|
|
2971
|
+
d = this.runTasks(d, tasks);
|
|
2972
|
+
if (select) d = pick5(d, [...normalizeSelect(select), ...this.baseFieldsExt]);
|
|
2973
|
+
else if (!returningAll) d = pick5(d, [...Object.keys(data), "_id"]);
|
|
2974
|
+
return d;
|
|
2253
2975
|
}
|
|
2254
2976
|
);
|
|
2255
2977
|
if (!result.success) {
|
|
@@ -2260,7 +2982,7 @@ var PublicService = class extends Service {
|
|
|
2260
2982
|
async _upsert(data, args, options) {
|
|
2261
2983
|
const idKey = this.getIdentifier();
|
|
2262
2984
|
if (idKey !== "_id") {
|
|
2263
|
-
return { success: false, code: "bad_request" /* BadRequest */, errors: ["not supported custom id field"] };
|
|
2985
|
+
return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: ["not supported custom id field"] };
|
|
2264
2986
|
}
|
|
2265
2987
|
const { [idKey]: idVal, ...otherData } = data;
|
|
2266
2988
|
const upsertId = typeof idVal === "string" ? idVal : void 0;
|
|
@@ -2270,7 +2992,7 @@ var PublicService = class extends Service {
|
|
|
2270
2992
|
return existing;
|
|
2271
2993
|
}
|
|
2272
2994
|
if (!existing.data) {
|
|
2273
|
-
return { success: false, code: "unauthorized" /* Unauthorized */, errors: ["Unauthorized"] };
|
|
2995
|
+
return { success: false, kind: "error", code: "unauthorized" /* Unauthorized */, errors: ["Unauthorized"] };
|
|
2274
2996
|
}
|
|
2275
2997
|
return this._update(upsertId, otherData, args, options);
|
|
2276
2998
|
}
|
|
@@ -2380,7 +3102,7 @@ var DataService = class {
|
|
|
2380
3102
|
}
|
|
2381
3103
|
async findOne(filter2, args, options) {
|
|
2382
3104
|
const filterErrors = validateClientFilter(filter2);
|
|
2383
|
-
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
3105
|
+
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
2384
3106
|
const { select } = args ?? {};
|
|
2385
3107
|
const { access = "read" } = options ?? {};
|
|
2386
3108
|
let [_filter, _select] = await Promise.all([this.genFilter(access, filter2), this.genQuerySelect(access, select)]);
|
|
@@ -2388,9 +3110,9 @@ var DataService = class {
|
|
|
2388
3110
|
filter: _filter,
|
|
2389
3111
|
select: _select
|
|
2390
3112
|
};
|
|
2391
|
-
if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
3113
|
+
if (_filter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
2392
3114
|
let doc = await findElement(this.data, _filter);
|
|
2393
|
-
if (!doc) return { success: false, code: "not_found" /* NotFound */, query };
|
|
3115
|
+
if (!doc) return { success: false, kind: "error", code: "not_found" /* NotFound */, query };
|
|
2394
3116
|
doc = await this.trimOutputFields(doc, access);
|
|
2395
3117
|
if (_select.length > 0) doc = pick6(doc, _select);
|
|
2396
3118
|
return { success: true, kind: "single", code: "success" /* Success */, data: doc, query };
|
|
@@ -2409,7 +3131,7 @@ var DataService = class {
|
|
|
2409
3131
|
}
|
|
2410
3132
|
async find(filter2, args, options) {
|
|
2411
3133
|
const filterErrors = validateClientFilter(filter2);
|
|
2412
|
-
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
3134
|
+
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
2413
3135
|
const { select, sort, skip, limit, page, pageSize } = args ?? {};
|
|
2414
3136
|
const [_filter, _select, pagination] = await Promise.all([
|
|
2415
3137
|
this.genFilter("list", filter2),
|
|
@@ -2422,27 +3144,33 @@ var DataService = class {
|
|
|
2422
3144
|
sort,
|
|
2423
3145
|
...pagination
|
|
2424
3146
|
};
|
|
2425
|
-
if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
3147
|
+
if (_filter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
3148
|
+
if (sort) {
|
|
3149
|
+
const sortFieldErrors = await this.validateSortFields(sort, "list");
|
|
3150
|
+
if (sortFieldErrors.length > 0) {
|
|
3151
|
+
return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: sortFieldErrors, query };
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
2426
3154
|
let docs = await filterCollection(this.data, _filter);
|
|
2427
3155
|
const totalCount = docs.length;
|
|
2428
|
-
|
|
2429
|
-
|
|
3156
|
+
if (sort) {
|
|
3157
|
+
const { sortKey, sortOrder } = parseSortString(sort);
|
|
3158
|
+
docs = orderBy(docs, [sortKey], [sortOrder]);
|
|
3159
|
+
}
|
|
3160
|
+
const pagedDocs = docs.slice(query.skip, query.limit && query.skip + query.limit);
|
|
3161
|
+
const trimmed = await Promise.all(
|
|
3162
|
+
pagedDocs.map(async (doc) => {
|
|
2430
3163
|
doc = await this.trimOutputFields(doc, "list");
|
|
2431
3164
|
if (_select.length > 0) doc = pick6(doc, _select);
|
|
2432
3165
|
return doc;
|
|
2433
3166
|
})
|
|
2434
3167
|
);
|
|
2435
|
-
if (sort) {
|
|
2436
|
-
const { sortKey, sortOrder } = parseSortString(sort);
|
|
2437
|
-
docs = orderBy(docs, [sortKey], [sortOrder]);
|
|
2438
|
-
}
|
|
2439
|
-
docs = docs.slice(query.skip, query.limit && query.skip + query.limit);
|
|
2440
3168
|
return {
|
|
2441
3169
|
success: true,
|
|
2442
3170
|
kind: "list",
|
|
2443
3171
|
code: "success" /* Success */,
|
|
2444
|
-
data:
|
|
2445
|
-
count:
|
|
3172
|
+
data: trimmed,
|
|
3173
|
+
count: trimmed.length,
|
|
2446
3174
|
totalCount,
|
|
2447
3175
|
query
|
|
2448
3176
|
};
|
|
@@ -2474,33 +3202,101 @@ var DataService = class {
|
|
|
2474
3202
|
trimOutputFields(doc, access, baseFields) {
|
|
2475
3203
|
return this.pickAllowedFields(doc, access, baseFields);
|
|
2476
3204
|
}
|
|
3205
|
+
async validateSortFields(sort, access) {
|
|
3206
|
+
const fieldPathPattern = /^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)*$/;
|
|
3207
|
+
const fields = sort.trim().split(/\s+/).filter(Boolean).map((field) => ({
|
|
3208
|
+
raw: field,
|
|
3209
|
+
name: field.startsWith("-") ? field.slice(1) : field
|
|
3210
|
+
}));
|
|
3211
|
+
const errors = [];
|
|
3212
|
+
for (const { raw, name } of fields) {
|
|
3213
|
+
if (!fieldPathPattern.test(name)) {
|
|
3214
|
+
errors.push({ detail: `Invalid sort field: ${raw}`, pointer: `#/sort` });
|
|
3215
|
+
continue;
|
|
3216
|
+
}
|
|
3217
|
+
if (name === "id" || name === "_id") continue;
|
|
3218
|
+
const allowed = await this.genAllowedFields({}, access);
|
|
3219
|
+
if (!allowed.includes(name)) {
|
|
3220
|
+
errors.push({ detail: `Sort field is not allowed: ${name}`, pointer: `#/sort` });
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
return errors;
|
|
3224
|
+
}
|
|
2477
3225
|
};
|
|
2478
3226
|
|
|
2479
3227
|
// src/processors.ts
|
|
2480
|
-
import {
|
|
3228
|
+
import { cloneDeep, forEach as forEach6, get as get7, isArray as isArray8, isPlainObject as isPlainObject12, map as map2, set as set5 } from "@web-ts-toolkit/utils";
|
|
3229
|
+
var UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
3230
|
+
function failUnsafeSegment(path) {
|
|
3231
|
+
throw new Error(
|
|
3232
|
+
`copyAndDepopulate: refusing path '${path}' because it contains an unsafe segment (__proto__, prototype, or constructor).`
|
|
3233
|
+
);
|
|
3234
|
+
}
|
|
3235
|
+
function failMissingId(path) {
|
|
3236
|
+
throw new Error(`copyAndDepopulate: path '${path}' resolved to a record missing the configured id field.`);
|
|
3237
|
+
}
|
|
3238
|
+
function assertSafePath(path) {
|
|
3239
|
+
const segs = path.split(".");
|
|
3240
|
+
for (const seg of segs) {
|
|
3241
|
+
if (seg.length === 0) continue;
|
|
3242
|
+
if (UNSAFE_PATH_KEYS.has(seg)) {
|
|
3243
|
+
failUnsafeSegment(path);
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
2481
3247
|
var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
|
|
2482
|
-
const
|
|
2483
|
-
const idField =
|
|
2484
|
-
|
|
2485
|
-
|
|
3248
|
+
const mutable = options.mutable !== false;
|
|
3249
|
+
const idField = options.idField ?? "_id";
|
|
3250
|
+
const obj = mutable ? docObject : cloneDeep(docObject);
|
|
3251
|
+
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
3252
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
3253
|
+
const src = op.src;
|
|
3254
|
+
const dest = op.dest;
|
|
3255
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
3256
|
+
assertSafePath(src);
|
|
3257
|
+
assertSafePath(dest);
|
|
2486
3258
|
let targets = [obj];
|
|
2487
|
-
const segs =
|
|
3259
|
+
const segs = src.split(".");
|
|
2488
3260
|
forEach6(segs, (seg, ind) => {
|
|
2489
3261
|
if (segs.length === ind + 1) {
|
|
2490
3262
|
forEach6(targets, (target) => {
|
|
3263
|
+
if (!isPlainObject12(target)) return;
|
|
2491
3264
|
const targetObject = target;
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
3265
|
+
const leaf = targetObject[seg];
|
|
3266
|
+
const sourceForDest = leaf;
|
|
3267
|
+
let depopulated;
|
|
3268
|
+
if (isArray8(leaf)) {
|
|
3269
|
+
if (leaf.length === 0) {
|
|
3270
|
+
depopulated = [];
|
|
3271
|
+
} else if (leaf.every((item) => isPlainObject12(item))) {
|
|
3272
|
+
depopulated = map2(leaf, idField).map((id, i) => {
|
|
3273
|
+
if (id === void 0) {
|
|
3274
|
+
failMissingId(`${src}[${i}]`);
|
|
3275
|
+
}
|
|
3276
|
+
return id;
|
|
3277
|
+
});
|
|
3278
|
+
} else {
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3281
|
+
} else if (isPlainObject12(leaf)) {
|
|
3282
|
+
const id = get7(leaf, idField);
|
|
3283
|
+
if (id === void 0) {
|
|
3284
|
+
failMissingId(src);
|
|
3285
|
+
}
|
|
3286
|
+
depopulated = id;
|
|
3287
|
+
} else {
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
set5(targetObject, dest, sourceForDest);
|
|
3291
|
+
set5(targetObject, seg, depopulated);
|
|
2498
3292
|
});
|
|
2499
3293
|
} else {
|
|
2500
3294
|
targets = targets.reduce((ret, target) => {
|
|
3295
|
+
if (!isPlainObject12(target)) return ret;
|
|
2501
3296
|
const targetObject = target;
|
|
2502
|
-
|
|
2503
|
-
|
|
3297
|
+
const next = targetObject[seg];
|
|
3298
|
+
if (isArray8(next)) ret.push(...next);
|
|
3299
|
+
else if (next !== null && next !== void 0) ret.push(next);
|
|
2504
3300
|
return ret;
|
|
2505
3301
|
}, []);
|
|
2506
3302
|
}
|
|
@@ -2548,15 +3344,15 @@ var Cache = class {
|
|
|
2548
3344
|
|
|
2549
3345
|
// src/core-shared.ts
|
|
2550
3346
|
import {
|
|
2551
|
-
castArray as
|
|
3347
|
+
castArray as castArray3,
|
|
2552
3348
|
arrayToRecord,
|
|
2553
3349
|
hasOwn as hasOwn2,
|
|
2554
|
-
isArray as
|
|
3350
|
+
isArray as isArray9,
|
|
2555
3351
|
isBoolean as isBoolean4,
|
|
2556
3352
|
isEmpty as isEmpty2,
|
|
2557
3353
|
isEqual,
|
|
2558
3354
|
isFunction as isFunction4,
|
|
2559
|
-
isPlainObject as
|
|
3355
|
+
isPlainObject as isPlainObject13,
|
|
2560
3356
|
isString as isString5
|
|
2561
3357
|
} from "@web-ts-toolkit/utils";
|
|
2562
3358
|
|
|
@@ -2597,10 +3393,10 @@ var permission_default = Permission;
|
|
|
2597
3393
|
|
|
2598
3394
|
// src/core-shared.ts
|
|
2599
3395
|
function isAndFilter(filter2) {
|
|
2600
|
-
return !!filter2 &&
|
|
3396
|
+
return !!filter2 && isPlainObject13(filter2) && Object.keys(filter2).length === 1 && isArray9(filter2.$and);
|
|
2601
3397
|
}
|
|
2602
3398
|
function isMergeableClause(filter2) {
|
|
2603
|
-
return !!filter2 &&
|
|
3399
|
+
return !!filter2 && isPlainObject13(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
|
|
2604
3400
|
}
|
|
2605
3401
|
function optimizeAndFilter(clausesInput) {
|
|
2606
3402
|
const clauses = [];
|
|
@@ -2614,9 +3410,24 @@ function optimizeAndFilter(clausesInput) {
|
|
|
2614
3410
|
clauses.push(clause);
|
|
2615
3411
|
}
|
|
2616
3412
|
}
|
|
2617
|
-
const
|
|
2618
|
-
|
|
2619
|
-
|
|
3413
|
+
const dedupeKey = (value) => {
|
|
3414
|
+
if (isArray9(value)) {
|
|
3415
|
+
return `[${value.map(dedupeKey).join(",")}]`;
|
|
3416
|
+
}
|
|
3417
|
+
if (isPlainObject13(value)) {
|
|
3418
|
+
return `{${Object.keys(value).sort().map((key) => `${key}:${dedupeKey(value[key])}`).join(",")}}`;
|
|
3419
|
+
}
|
|
3420
|
+
return JSON.stringify(value);
|
|
3421
|
+
};
|
|
3422
|
+
const dedupedClauses = [];
|
|
3423
|
+
const seenClauses = /* @__PURE__ */ new Set();
|
|
3424
|
+
for (let x = 0; x < clauses.length; x++) {
|
|
3425
|
+
const clause = clauses[x];
|
|
3426
|
+
const key = dedupeKey(clause);
|
|
3427
|
+
if (seenClauses.has(key)) continue;
|
|
3428
|
+
seenClauses.add(key);
|
|
3429
|
+
dedupedClauses.push(clause);
|
|
3430
|
+
}
|
|
2620
3431
|
const mergedClause = {};
|
|
2621
3432
|
const remainingClauses = [];
|
|
2622
3433
|
for (let x = 0; x < dedupedClauses.length; x++) {
|
|
@@ -2645,7 +3456,7 @@ function optimizeAndFilter(clausesInput) {
|
|
|
2645
3456
|
}
|
|
2646
3457
|
function normalizeFilter(filter2) {
|
|
2647
3458
|
if (filter2 === false) return false;
|
|
2648
|
-
if (!filter2 || !
|
|
3459
|
+
if (!filter2 || !isPlainObject13(filter2) || isEmpty2(filter2)) return null;
|
|
2649
3460
|
if (!isAndFilter(filter2)) {
|
|
2650
3461
|
return filter2;
|
|
2651
3462
|
}
|
|
@@ -2697,8 +3508,8 @@ async function setRequestPermissions(req) {
|
|
|
2697
3508
|
const globalPermissions = getGlobalOption("globalPermissions");
|
|
2698
3509
|
if (!isFunction4(globalPermissions)) return;
|
|
2699
3510
|
const permissions = await globalPermissions.call(req, req);
|
|
2700
|
-
if (
|
|
2701
|
-
else if (
|
|
3511
|
+
if (isPlainObject13(permissions)) req[requestPermissionField] = permissions;
|
|
3512
|
+
else if (isArray9(permissions)) req[requestPermissionField] = arrayToRecord(permissions);
|
|
2702
3513
|
else if (isString5(permissions)) req[requestPermissionField] = { [permissions]: true };
|
|
2703
3514
|
}
|
|
2704
3515
|
async function evaluateRouteGuard(req, permissions, routeGuard) {
|
|
@@ -2710,7 +3521,7 @@ async function evaluateRouteGuard(req, permissions, routeGuard) {
|
|
|
2710
3521
|
if (isString5(routeGuard)) {
|
|
2711
3522
|
return stringHandler(routeGuard);
|
|
2712
3523
|
}
|
|
2713
|
-
if (
|
|
3524
|
+
if (isArray9(routeGuard)) {
|
|
2714
3525
|
return arrayHandler(routeGuard);
|
|
2715
3526
|
}
|
|
2716
3527
|
if (isFunction4(routeGuard)) {
|
|
@@ -2719,7 +3530,7 @@ async function evaluateRouteGuard(req, permissions, routeGuard) {
|
|
|
2719
3530
|
return false;
|
|
2720
3531
|
}
|
|
2721
3532
|
async function callHookChain(req, hook, doc, permissions, context) {
|
|
2722
|
-
const hooks =
|
|
3533
|
+
const hooks = castArray3(hook);
|
|
2723
3534
|
for (let x = 0; x < hooks.length; x++) {
|
|
2724
3535
|
if (isFunction4(hooks[x])) {
|
|
2725
3536
|
doc = await hooks[x].call(req, doc, permissions, context);
|
|
@@ -2748,7 +3559,7 @@ async function collectSchemaFields({
|
|
|
2748
3559
|
if (value) fields.push(key);
|
|
2749
3560
|
} else if (isString5(value)) {
|
|
2750
3561
|
if (stringHandler(value)) fields.push(key);
|
|
2751
|
-
} else if (
|
|
3562
|
+
} else if (isArray9(value)) {
|
|
2752
3563
|
if (arrayHandler(value)) fields.push(key);
|
|
2753
3564
|
} else if (isFunction4(value)) {
|
|
2754
3565
|
if (await value.call(req, ...functionArgs)) fields.push(key);
|
|
@@ -2784,7 +3595,7 @@ async function initializeAclRequest({
|
|
|
2784
3595
|
await core.setPermissions();
|
|
2785
3596
|
assignCore(core);
|
|
2786
3597
|
req[PERMISSIONS] = core.getPermissions();
|
|
2787
|
-
req[PERMISSION_KEYS] = req[PERMISSIONS].keys;
|
|
3598
|
+
req[PERMISSION_KEYS] = [...req[PERMISSIONS].keys];
|
|
2788
3599
|
req[flag] = true;
|
|
2789
3600
|
return core;
|
|
2790
3601
|
}
|
|
@@ -3028,6 +3839,14 @@ var Core = class {
|
|
|
3028
3839
|
if (!allowedParentPaths.includes(parentPath)) return null;
|
|
3029
3840
|
const refModelName = getModelRef(modelName, ret.path);
|
|
3030
3841
|
if (!refModelName) return null;
|
|
3842
|
+
const runtime = getActiveRuntime();
|
|
3843
|
+
if (runtime && !runtime.hasModel(refModelName)) {
|
|
3844
|
+
return null;
|
|
3845
|
+
}
|
|
3846
|
+
const allowedTargetOperation = await this.req.macl.isAllowed(refModelName, populateAccess);
|
|
3847
|
+
if (!allowedTargetOperation) {
|
|
3848
|
+
return null;
|
|
3849
|
+
}
|
|
3031
3850
|
ret.select = await this.genSelect(refModelName, populateAccess, ret.select, false);
|
|
3032
3851
|
const filter2 = await this.genFilter(refModelName, populateAccess, null);
|
|
3033
3852
|
if (filter2 === false) return null;
|
|
@@ -3043,7 +3862,7 @@ var Core = class {
|
|
|
3043
3862
|
if (isFunction5(validate)) {
|
|
3044
3863
|
const permissions = this.getGlobalPermissions();
|
|
3045
3864
|
return validate.call(this.req, allowedData, permissions, context);
|
|
3046
|
-
} else if (isBoolean5(validate) ||
|
|
3865
|
+
} else if (isBoolean5(validate) || isArray10(validate)) {
|
|
3047
3866
|
return validate;
|
|
3048
3867
|
} else {
|
|
3049
3868
|
return true;
|
|
@@ -3096,10 +3915,13 @@ var Core = class {
|
|
|
3096
3915
|
const permissions = this.getGlobalPermissions();
|
|
3097
3916
|
try {
|
|
3098
3917
|
docPermissions = await docPermissionsFn.call(this.req, doc, permissions, context);
|
|
3099
|
-
} catch (
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3918
|
+
} catch (error2) {
|
|
3919
|
+
warn("docPermissions hook failed; applying empty document permissions", {
|
|
3920
|
+
modelName,
|
|
3921
|
+
access,
|
|
3922
|
+
operation: context.operation,
|
|
3923
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
3924
|
+
});
|
|
3103
3925
|
}
|
|
3104
3926
|
}
|
|
3105
3927
|
return docPermissions;
|
|
@@ -3172,7 +3994,7 @@ var Core = class {
|
|
|
3172
3994
|
return runDecorateAllHook({ req: this.req, hook: decorateAll, docs, permissions, context });
|
|
3173
3995
|
}
|
|
3174
3996
|
runTasks(modelName, docObject, task) {
|
|
3175
|
-
const tasks = compact3(
|
|
3997
|
+
const tasks = compact3(castArray4(task));
|
|
3176
3998
|
if (tasks.length === 0) return docObject;
|
|
3177
3999
|
forEach7(tasks, (task2) => {
|
|
3178
4000
|
const { type, args, options } = task2;
|
|
@@ -3257,19 +4079,19 @@ function guard(condition) {
|
|
|
3257
4079
|
const permissions = req[PERMISSIONS];
|
|
3258
4080
|
let cond = condition;
|
|
3259
4081
|
let phas = (key) => permissions.has(key);
|
|
3260
|
-
if (
|
|
4082
|
+
if (isPlainObject14(condition)) {
|
|
3261
4083
|
const { modelName, id, condition: _cond } = condition;
|
|
3262
4084
|
const svc = req.macl.getPublicService(modelName);
|
|
3263
4085
|
const select = getModelOption(modelName, `alwaysSelectFields.read`, void 0);
|
|
3264
4086
|
let _id = isString7(id) ? id : null;
|
|
3265
|
-
if (
|
|
4087
|
+
if (isPlainObject14(id)) {
|
|
3266
4088
|
const { type = "param", key } = id;
|
|
3267
4089
|
if (type === "param") {
|
|
3268
4090
|
const paramValue = req.params[key];
|
|
3269
|
-
_id =
|
|
4091
|
+
_id = isArray11(paramValue) ? paramValue[0] ?? null : paramValue ?? null;
|
|
3270
4092
|
} else if (type === "query") {
|
|
3271
4093
|
const _qval = req.query[key];
|
|
3272
|
-
if (
|
|
4094
|
+
if (isArray11(_qval)) _id = _qval.length > 0 ? _qval[0] : null;
|
|
3273
4095
|
else _id = _qval;
|
|
3274
4096
|
} else {
|
|
3275
4097
|
_id = null;
|
|
@@ -3289,7 +4111,7 @@ function guard(condition) {
|
|
|
3289
4111
|
const { stringHandler, arrayHandler } = createValidator(phas);
|
|
3290
4112
|
if (isString7(cond)) {
|
|
3291
4113
|
if (stringHandler(cond)) return next();
|
|
3292
|
-
} else if (
|
|
4114
|
+
} else if (isArray11(cond)) {
|
|
3293
4115
|
if (arrayHandler(cond)) return next();
|
|
3294
4116
|
} else if (isFunction6(cond)) {
|
|
3295
4117
|
const result = await cond.call(req, permissions);
|
|
@@ -3304,7 +4126,7 @@ import express2 from "express";
|
|
|
3304
4126
|
|
|
3305
4127
|
// src/routers/model-router.ts
|
|
3306
4128
|
import JsonRouter5 from "@web-ts-toolkit/express-json-router";
|
|
3307
|
-
import { forEach as forEach8, isPlainObject as
|
|
4129
|
+
import { forEach as forEach8, isPlainObject as isPlainObject15, isString as isString8, isUndefined as isUndefined2, normalizeUrlPath as normalizeUrlPath2, padEnd } from "@web-ts-toolkit/utils";
|
|
3308
4130
|
|
|
3309
4131
|
// src/routers/router-mutation.ts
|
|
3310
4132
|
var MODEL_BUILD_TIME_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -3334,9 +4156,37 @@ function assertMutableRouterOptions(kind, options) {
|
|
|
3334
4156
|
// src/http/response-pipelines/model-response.ts
|
|
3335
4157
|
import JsonRouter3 from "@web-ts-toolkit/express-json-router";
|
|
3336
4158
|
|
|
4159
|
+
// src/http/response-pipelines/service-result.ts
|
|
4160
|
+
var toPublicErrorResult = (result) => ({
|
|
4161
|
+
success: false,
|
|
4162
|
+
code: result.code,
|
|
4163
|
+
errors: result.errors
|
|
4164
|
+
});
|
|
4165
|
+
var toPublicSingleResult = (result) => ({
|
|
4166
|
+
success: true,
|
|
4167
|
+
kind: "single",
|
|
4168
|
+
code: result.code,
|
|
4169
|
+
data: result.data
|
|
4170
|
+
});
|
|
4171
|
+
var toPublicListResult = (result) => ({
|
|
4172
|
+
success: true,
|
|
4173
|
+
kind: "list",
|
|
4174
|
+
code: result.code,
|
|
4175
|
+
data: result.data,
|
|
4176
|
+
count: result.count,
|
|
4177
|
+
totalCount: result.totalCount ?? null
|
|
4178
|
+
});
|
|
4179
|
+
var toPublicServiceResult = (result) => {
|
|
4180
|
+
if (result.kind === "error") {
|
|
4181
|
+
return toPublicErrorResult(result);
|
|
4182
|
+
}
|
|
4183
|
+
return result.kind === "list" ? toPublicListResult(result) : toPublicSingleResult(result);
|
|
4184
|
+
};
|
|
4185
|
+
|
|
3337
4186
|
// src/http/response-pipelines/list-response.ts
|
|
3338
4187
|
var formatListResponse = (req, result, includeCount, includeExtraHeaders) => {
|
|
3339
|
-
const
|
|
4188
|
+
const publicResult = toPublicListResult(result);
|
|
4189
|
+
const { data, count: returnedCount, totalCount } = publicResult;
|
|
3340
4190
|
const query = result.query ?? {};
|
|
3341
4191
|
const rawLimit = Number(query.limit ?? 0);
|
|
3342
4192
|
const skip = Number(query.skip ?? 0);
|
|
@@ -3371,16 +4221,18 @@ var formatListResponse = (req, result, includeCount, includeExtraHeaders) => {
|
|
|
3371
4221
|
return { data, meta };
|
|
3372
4222
|
};
|
|
3373
4223
|
var formatCreatedData = (result) => {
|
|
3374
|
-
|
|
4224
|
+
const publicResult = toPublicListResult(result);
|
|
4225
|
+
return publicResult.count === 1 ? publicResult.data[0] : publicResult.data;
|
|
3375
4226
|
};
|
|
3376
4227
|
var formatUpsertCreatedData = (result) => {
|
|
3377
|
-
|
|
4228
|
+
const publicResult = toPublicListResult(result);
|
|
4229
|
+
return publicResult.data.length > 0 ? publicResult.data[0] : null;
|
|
3378
4230
|
};
|
|
3379
4231
|
|
|
3380
4232
|
// src/http/response-pipelines/model-response.ts
|
|
3381
4233
|
var success = JsonRouter3.success;
|
|
3382
4234
|
function unwrapServiceData(result) {
|
|
3383
|
-
return result.data;
|
|
4235
|
+
return result.kind === "list" ? toPublicListResult(result).data : toPublicSingleResult(result).data;
|
|
3384
4236
|
}
|
|
3385
4237
|
function formatModelListResponse(req, result, includeCount, includeExtraHeaders) {
|
|
3386
4238
|
return formatListResponse(req, result, includeCount, includeExtraHeaders);
|
|
@@ -3389,7 +4241,13 @@ function formatModelCreatedResponse(result) {
|
|
|
3389
4241
|
return new success.Created(formatCreatedData(result));
|
|
3390
4242
|
}
|
|
3391
4243
|
function formatModelUpsertResponse(result) {
|
|
3392
|
-
|
|
4244
|
+
if (result.kind === "list") {
|
|
4245
|
+
return new success.Created(formatUpsertCreatedData(result));
|
|
4246
|
+
}
|
|
4247
|
+
if (result.kind === "single") {
|
|
4248
|
+
return toPublicSingleResult(result).data;
|
|
4249
|
+
}
|
|
4250
|
+
throw new Error(`Unexpected ServiceResult kind: ${String(result.kind)}`);
|
|
3393
4251
|
}
|
|
3394
4252
|
|
|
3395
4253
|
// src/validation/parsers.ts
|
|
@@ -3398,9 +4256,12 @@ import JsonRouter4 from "@web-ts-toolkit/express-json-router";
|
|
|
3398
4256
|
// src/validation/common.ts
|
|
3399
4257
|
import { z as z2 } from "zod";
|
|
3400
4258
|
var stringOrStringArray = z2.union([z2.string(), z2.array(z2.string())]);
|
|
4259
|
+
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);
|
|
3401
4260
|
var queryBooleanString = z2.enum(["true", "false"]);
|
|
3402
|
-
var
|
|
3403
|
-
var
|
|
4261
|
+
var nonNegativeIntegerString = safeIntegerString(0, "Expected a non-negative integer");
|
|
4262
|
+
var positiveIntegerString = safeIntegerString(1, "Expected a positive integer");
|
|
4263
|
+
var nonNegativeIntegerSchema = z2.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
4264
|
+
var positiveIntegerSchema = z2.number().int().min(1).max(Number.MAX_SAFE_INTEGER);
|
|
3404
4265
|
var unknownRecord = z2.record(z2.string(), z2.unknown());
|
|
3405
4266
|
var projectionObjectSchema = z2.record(z2.string(), z2.union([z2.literal(1), z2.literal(-1)]));
|
|
3406
4267
|
var projectionSchema = z2.union([z2.string(), z2.array(z2.string()), projectionObjectSchema]);
|
|
@@ -3501,6 +4362,10 @@ function parsePathParam(value, parameter) {
|
|
|
3501
4362
|
return param;
|
|
3502
4363
|
}
|
|
3503
4364
|
function parseQuery(schema, value) {
|
|
4365
|
+
const complexityErrors = validateRequestComplexity(value, getGlobalOption("requestComplexity"), "request");
|
|
4366
|
+
if (complexityErrors.length > 0) {
|
|
4367
|
+
throw new clientErrors.BadRequestError("Bad Request", { errors: complexityErrors });
|
|
4368
|
+
}
|
|
3504
4369
|
const result = schema.safeParse(value);
|
|
3505
4370
|
if (!result.success) {
|
|
3506
4371
|
throwValidationError(normalizeIssues(result.error.issues), void 0, "parameter");
|
|
@@ -3508,6 +4373,10 @@ function parseQuery(schema, value) {
|
|
|
3508
4373
|
return result.data;
|
|
3509
4374
|
}
|
|
3510
4375
|
function parseBody(schema, value) {
|
|
4376
|
+
const complexityErrors = validateRequestComplexity(value ?? {}, getGlobalOption("requestComplexity"), "request");
|
|
4377
|
+
if (complexityErrors.length > 0) {
|
|
4378
|
+
throw new clientErrors.BadRequestError("Bad Request", { errors: complexityErrors });
|
|
4379
|
+
}
|
|
3511
4380
|
const result = schema.safeParse(value ?? {});
|
|
3512
4381
|
if (!result.success) {
|
|
3513
4382
|
throwValidationError(normalizeIssues(result.error.issues), void 0, "pointer");
|
|
@@ -3610,10 +4479,10 @@ function fromYup(schema) {
|
|
|
3610
4479
|
success: true,
|
|
3611
4480
|
data
|
|
3612
4481
|
};
|
|
3613
|
-
} catch (
|
|
4482
|
+
} catch (error2) {
|
|
3614
4483
|
return {
|
|
3615
4484
|
success: false,
|
|
3616
|
-
issues: normalizeYupIssues(
|
|
4485
|
+
issues: normalizeYupIssues(error2)
|
|
3617
4486
|
};
|
|
3618
4487
|
}
|
|
3619
4488
|
};
|
|
@@ -3716,10 +4585,10 @@ function fromVine(validator) {
|
|
|
3716
4585
|
success: true,
|
|
3717
4586
|
data: output
|
|
3718
4587
|
};
|
|
3719
|
-
} catch (
|
|
4588
|
+
} catch (error2) {
|
|
3720
4589
|
return {
|
|
3721
4590
|
success: false,
|
|
3722
|
-
issues: normalizeVineError(
|
|
4591
|
+
issues: normalizeVineError(error2)
|
|
3723
4592
|
};
|
|
3724
4593
|
}
|
|
3725
4594
|
};
|
|
@@ -3740,11 +4609,11 @@ function normalizePathSegment(segment) {
|
|
|
3740
4609
|
function isStandardSchemaPathSegment(segment) {
|
|
3741
4610
|
return typeof segment === "object" && segment !== null && "key" in segment;
|
|
3742
4611
|
}
|
|
3743
|
-
function normalizeYupIssues(
|
|
3744
|
-
if (!isYupValidationError(
|
|
4612
|
+
function normalizeYupIssues(error2) {
|
|
4613
|
+
if (!isYupValidationError(error2)) {
|
|
3745
4614
|
return [{ message: "Validation failed" }];
|
|
3746
4615
|
}
|
|
3747
|
-
const issues =
|
|
4616
|
+
const issues = error2.inner?.length ? error2.inner : [error2];
|
|
3748
4617
|
return issues.map((issue) => ({
|
|
3749
4618
|
message: issue.message,
|
|
3750
4619
|
path: parsePathString(issue.path)
|
|
@@ -3795,11 +4664,11 @@ function normalizeSuperstructPath(failure) {
|
|
|
3795
4664
|
if (typeof failure.key === "string" || typeof failure.key === "number") return [failure.key];
|
|
3796
4665
|
return void 0;
|
|
3797
4666
|
}
|
|
3798
|
-
function normalizeVineError(
|
|
3799
|
-
if (!isVineValidationError(
|
|
4667
|
+
function normalizeVineError(error2) {
|
|
4668
|
+
if (!isVineValidationError(error2)) {
|
|
3800
4669
|
return [{ message: "Validation failed" }];
|
|
3801
4670
|
}
|
|
3802
|
-
return
|
|
4671
|
+
return error2.messages.map((message) => ({
|
|
3803
4672
|
message: message.message,
|
|
3804
4673
|
path: normalizeVineField(message)
|
|
3805
4674
|
}));
|
|
@@ -3822,14 +4691,14 @@ function parsePathString(path) {
|
|
|
3822
4691
|
if (!path) return void 0;
|
|
3823
4692
|
return path.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean).map((segment) => /^\d+$/.test(segment) ? Number(segment) : segment);
|
|
3824
4693
|
}
|
|
3825
|
-
function isYupValidationError(
|
|
3826
|
-
return typeof
|
|
4694
|
+
function isYupValidationError(error2) {
|
|
4695
|
+
return typeof error2 === "object" && error2 !== null && "message" in error2;
|
|
3827
4696
|
}
|
|
3828
4697
|
function isArkTypeErrors(value) {
|
|
3829
4698
|
return Array.isArray(value);
|
|
3830
4699
|
}
|
|
3831
|
-
function isVineValidationError(
|
|
3832
|
-
return typeof
|
|
4700
|
+
function isVineValidationError(error2) {
|
|
4701
|
+
return typeof error2 === "object" && error2 !== null && "messages" in error2 && Array.isArray(error2.messages);
|
|
3833
4702
|
}
|
|
3834
4703
|
function formatIssue(issue, key, location = "pointer") {
|
|
3835
4704
|
const path = (issue.path ?? []).map(String);
|
|
@@ -3852,9 +4721,9 @@ function buildPointer(path) {
|
|
|
3852
4721
|
// src/validation/model-router.ts
|
|
3853
4722
|
import { z as z3 } from "zod";
|
|
3854
4723
|
var listQuerySchema = z3.object({
|
|
3855
|
-
skip:
|
|
4724
|
+
skip: nonNegativeIntegerString.optional(),
|
|
3856
4725
|
limit: positiveIntegerString.optional(),
|
|
3857
|
-
page:
|
|
4726
|
+
page: nonNegativeIntegerString.optional(),
|
|
3858
4727
|
page_size: positiveIntegerString.optional(),
|
|
3859
4728
|
skim: queryBooleanString.optional(),
|
|
3860
4729
|
include_permissions: queryBooleanString.optional(),
|
|
@@ -3883,10 +4752,10 @@ var listBodySchema = z3.object({
|
|
|
3883
4752
|
populate: populateSchema.optional(),
|
|
3884
4753
|
include: includeSchema.optional(),
|
|
3885
4754
|
tasks: tasksSchema.optional(),
|
|
3886
|
-
skip: z3.union([nonNegativeIntegerSchema,
|
|
3887
|
-
limit: z3.union([
|
|
3888
|
-
page: z3.union([nonNegativeIntegerSchema,
|
|
3889
|
-
pageSize: z3.union([
|
|
4755
|
+
skip: z3.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
4756
|
+
limit: z3.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
4757
|
+
page: z3.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
4758
|
+
pageSize: z3.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
3890
4759
|
options: z3.object({
|
|
3891
4760
|
skim: z3.boolean().optional(),
|
|
3892
4761
|
includePermissions: z3.boolean().optional(),
|
|
@@ -3896,11 +4765,8 @@ var listBodySchema = z3.object({
|
|
|
3896
4765
|
}).passthrough().optional()
|
|
3897
4766
|
}).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query"]));
|
|
3898
4767
|
var countBodySchema = z3.object({
|
|
3899
|
-
filter: objectOrArraySchema.optional()
|
|
3900
|
-
|
|
3901
|
-
access: z3.unknown().optional()
|
|
3902
|
-
}).passthrough().optional()
|
|
3903
|
-
}).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query", "access"]));
|
|
4768
|
+
filter: objectOrArraySchema.optional()
|
|
4769
|
+
}).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query", "access", "options"]));
|
|
3904
4770
|
var readFilterBodySchema = z3.object({
|
|
3905
4771
|
filter: objectOrArraySchema.optional(),
|
|
3906
4772
|
select: projectionSchema.optional(),
|
|
@@ -3994,10 +4860,10 @@ var dataListBodySchema = z4.object({
|
|
|
3994
4860
|
filter: objectOrArraySchema.optional(),
|
|
3995
4861
|
select: projectionSchema.optional(),
|
|
3996
4862
|
sort: z4.string().optional(),
|
|
3997
|
-
skip: z4.union([nonNegativeIntegerSchema,
|
|
3998
|
-
limit: z4.union([
|
|
3999
|
-
page: z4.union([nonNegativeIntegerSchema,
|
|
4000
|
-
pageSize: z4.union([
|
|
4863
|
+
skip: z4.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
4864
|
+
limit: z4.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
4865
|
+
page: z4.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
4866
|
+
pageSize: z4.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
4001
4867
|
options: z4.object({
|
|
4002
4868
|
includeCount: z4.boolean().optional(),
|
|
4003
4869
|
includeExtraHeaders: z4.boolean().optional()
|
|
@@ -4016,17 +4882,17 @@ import { z as z5 } from "zod";
|
|
|
4016
4882
|
var rootEntryBaseSchema = {
|
|
4017
4883
|
target: z5.enum(["model", "data"]),
|
|
4018
4884
|
name: z5.string().min(1),
|
|
4019
|
-
order:
|
|
4885
|
+
order: nonNegativeIntegerSchema.optional()
|
|
4020
4886
|
};
|
|
4021
4887
|
var rootModelListArgsSchema = z5.object({
|
|
4022
4888
|
select: projectionSchema.optional(),
|
|
4023
4889
|
populate: populateSchema.optional(),
|
|
4024
4890
|
include: includeSchema.optional(),
|
|
4025
4891
|
sort: sortSchema.optional(),
|
|
4026
|
-
skip: z5.union([nonNegativeIntegerSchema,
|
|
4027
|
-
limit: z5.union([
|
|
4028
|
-
page: z5.union([nonNegativeIntegerSchema,
|
|
4029
|
-
pageSize: z5.union([
|
|
4892
|
+
skip: z5.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
4893
|
+
limit: z5.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
4894
|
+
page: z5.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
4895
|
+
pageSize: z5.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
4030
4896
|
tasks: tasksSchema.optional()
|
|
4031
4897
|
}).passthrough();
|
|
4032
4898
|
var rootModelListOptionsSchema = z5.object({
|
|
@@ -4082,16 +4948,14 @@ var rootModelSubReadArgsSchema = z5.object({
|
|
|
4082
4948
|
select: fieldsSchema.optional(),
|
|
4083
4949
|
populate: subPopulateSchema.optional()
|
|
4084
4950
|
}).passthrough();
|
|
4085
|
-
var rootModelCountOptionsSchema = z5.object({
|
|
4086
|
-
access: z5.unknown().optional()
|
|
4087
|
-
}).passthrough();
|
|
4951
|
+
var rootModelCountOptionsSchema = z5.object({}).strict().optional();
|
|
4088
4952
|
var rootDataListArgsSchema = z5.object({
|
|
4089
4953
|
select: projectionSchema.optional(),
|
|
4090
4954
|
sort: z5.string().optional(),
|
|
4091
|
-
skip: z5.union([nonNegativeIntegerSchema,
|
|
4092
|
-
limit: z5.union([
|
|
4093
|
-
page: z5.union([nonNegativeIntegerSchema,
|
|
4094
|
-
pageSize: z5.union([
|
|
4955
|
+
skip: z5.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
4956
|
+
limit: z5.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
4957
|
+
page: z5.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
4958
|
+
pageSize: z5.union([positiveIntegerSchema, positiveIntegerString]).optional()
|
|
4095
4959
|
}).passthrough();
|
|
4096
4960
|
var rootDataListOptionsSchema = z5.object({
|
|
4097
4961
|
includeCount: z5.boolean().optional()
|
|
@@ -4099,8 +4963,21 @@ var rootDataListOptionsSchema = z5.object({
|
|
|
4099
4963
|
var rootDataReadArgsSchema = z5.object({
|
|
4100
4964
|
select: projectionSchema.optional()
|
|
4101
4965
|
}).passthrough();
|
|
4966
|
+
var rootModelNewArgsSchema = z5.object({
|
|
4967
|
+
select: projectionSchema.optional()
|
|
4968
|
+
}).passthrough();
|
|
4969
|
+
var rootModelNewOptionsSchema = z5.object({
|
|
4970
|
+
skim: z5.boolean().optional(),
|
|
4971
|
+
includePermissions: z5.boolean().optional()
|
|
4972
|
+
}).passthrough();
|
|
4102
4973
|
var rootModelQueryEntrySchema = z5.union([
|
|
4103
|
-
z5.object({
|
|
4974
|
+
z5.object({
|
|
4975
|
+
...rootEntryBaseSchema,
|
|
4976
|
+
target: z5.literal("model"),
|
|
4977
|
+
op: z5.literal("new"),
|
|
4978
|
+
args: rootModelNewArgsSchema.optional(),
|
|
4979
|
+
options: rootModelNewOptionsSchema.optional()
|
|
4980
|
+
}).passthrough(),
|
|
4104
4981
|
z5.object({
|
|
4105
4982
|
...rootEntryBaseSchema,
|
|
4106
4983
|
target: z5.literal("model"),
|
|
@@ -4215,7 +5092,7 @@ var rootModelQueryEntrySchema = z5.union([
|
|
|
4215
5092
|
op: z5.literal("count"),
|
|
4216
5093
|
filter: objectOrArraySchema.optional(),
|
|
4217
5094
|
options: rootModelCountOptionsSchema.optional()
|
|
4218
|
-
}).passthrough()
|
|
5095
|
+
}).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["access"]))
|
|
4219
5096
|
]);
|
|
4220
5097
|
var rootDataQueryEntrySchema = z5.union([
|
|
4221
5098
|
z5.object({
|
|
@@ -4374,8 +5251,9 @@ function setModelCollectionRoutes(context) {
|
|
|
4374
5251
|
body: defineOpenApiSchemaResolver(getAdvancedCreateOpenApiBody)
|
|
4375
5252
|
});
|
|
4376
5253
|
router.get("/new", async (req) => {
|
|
5254
|
+
await context.assertAllowed(req, "new");
|
|
4377
5255
|
const svc = context.getPublicService(req);
|
|
4378
|
-
const result = await svc._new();
|
|
5256
|
+
const result = await svc._new({}, { includePermissions: false });
|
|
4379
5257
|
handleResultError(result);
|
|
4380
5258
|
return unwrapServiceData(result);
|
|
4381
5259
|
});
|
|
@@ -4423,16 +5301,13 @@ function setModelDocumentRoutes(context) {
|
|
|
4423
5301
|
});
|
|
4424
5302
|
router.post("/count", async (req) => {
|
|
4425
5303
|
await context.assertAllowed(req, "count");
|
|
4426
|
-
const { filter: filter2
|
|
5304
|
+
const { filter: filter2 } = await parseBodyWithSchema(
|
|
4427
5305
|
countBodySchema,
|
|
4428
5306
|
req.body,
|
|
4429
5307
|
context.getRequestSchema("requestSchemas.count")
|
|
4430
5308
|
);
|
|
4431
5309
|
const svc = context.getPublicService(req);
|
|
4432
|
-
const result = await svc._count(
|
|
4433
|
-
filter2 ?? {},
|
|
4434
|
-
countOptions.access
|
|
4435
|
-
);
|
|
5310
|
+
const result = await svc._count(filter2 ?? {});
|
|
4436
5311
|
handleResultError(result);
|
|
4437
5312
|
return unwrapServiceData(result);
|
|
4438
5313
|
});
|
|
@@ -4710,7 +5585,6 @@ function setModelDocumentRoutes(context) {
|
|
|
4710
5585
|
}
|
|
4711
5586
|
|
|
4712
5587
|
// src/routers/model-router-subdocument-routes.ts
|
|
4713
|
-
import { isString as isString8 } from "@web-ts-toolkit/utils";
|
|
4714
5588
|
function setModelSubDocumentRoutes(context) {
|
|
4715
5589
|
const subs = getModelSub(context.modelName);
|
|
4716
5590
|
for (let x = 0; x < subs.length; x++) {
|
|
@@ -4740,7 +5614,10 @@ function setModelSubDocumentRoutes(context) {
|
|
|
4740
5614
|
context.getRequestSchema("requestSchemas.subList")
|
|
4741
5615
|
);
|
|
4742
5616
|
const svc = context.getPublicService(req);
|
|
4743
|
-
const result = await svc.listSub(id, sub, {
|
|
5617
|
+
const result = await svc.listSub(id, sub, {
|
|
5618
|
+
filter: body.filter ?? {},
|
|
5619
|
+
select: body.select ?? []
|
|
5620
|
+
});
|
|
4744
5621
|
handleResultError(result);
|
|
4745
5622
|
return unwrapServiceData(result);
|
|
4746
5623
|
}
|
|
@@ -4800,10 +5677,11 @@ function setModelSubDocumentRoutes(context) {
|
|
|
4800
5677
|
req.body,
|
|
4801
5678
|
context.getRequestSchema("requestSchemas.subRead")
|
|
4802
5679
|
);
|
|
4803
|
-
const populate = body.populate;
|
|
4804
|
-
const normalizedPopulate = Array.isArray(populate) ? populate.map((item) => isString8(item) ? { path: item } : item) : isString8(populate) ? { path: populate } : populate ?? [];
|
|
4805
5680
|
const svc = context.getPublicService(req);
|
|
4806
|
-
const result = await svc.readSub(id, sub, subId, {
|
|
5681
|
+
const result = await svc.readSub(id, sub, subId, {
|
|
5682
|
+
select: body.select ?? [],
|
|
5683
|
+
populate: normalizeSubPopulate(body.populate)
|
|
5684
|
+
});
|
|
4807
5685
|
handleResultError(result);
|
|
4808
5686
|
return unwrapServiceData(result);
|
|
4809
5687
|
}
|
|
@@ -4897,7 +5775,7 @@ function setOption(parentKey, optionKey, option) {
|
|
|
4897
5775
|
this.runtime.setModelOption(this.modelName, key, value);
|
|
4898
5776
|
return this;
|
|
4899
5777
|
}
|
|
4900
|
-
var ModelRouter = class {
|
|
5778
|
+
var ModelRouter = class _ModelRouter {
|
|
4901
5779
|
constructor(modelName, initialOptions, runtime = defaultRuntime) {
|
|
4902
5780
|
/**
|
|
4903
5781
|
* The maximum limit of the number of documents returned from the `list` operation.
|
|
@@ -5051,17 +5929,21 @@ var ModelRouter = class {
|
|
|
5051
5929
|
);
|
|
5052
5930
|
this.runtime = runtime;
|
|
5053
5931
|
this.runtime.setModelOptions(modelName, initialOptions);
|
|
5054
|
-
attachRuntimeToModel(modelName, this.runtime);
|
|
5055
5932
|
this.options = this.runtime.getModelOptions(modelName);
|
|
5056
5933
|
this.fullBasePath = normalizeUrlPath2(this.options.parentPath + this.options.basePath);
|
|
5057
5934
|
this.modelName = modelName;
|
|
5058
5935
|
this.router = new JsonRouter5(this.options.basePath, createSetCore(this.runtime), accessRouterResponseHandler);
|
|
5059
|
-
this.model = new model_default(modelName);
|
|
5936
|
+
this.model = new model_default(modelName, this.runtime);
|
|
5060
5937
|
this.setCollectionRoutes();
|
|
5061
5938
|
this.setDocumentRoutes();
|
|
5062
5939
|
this.setSubDocumentRoutes();
|
|
5063
5940
|
this.logEndpoints();
|
|
5064
5941
|
}
|
|
5942
|
+
static fromModel(model, initialOptions, runtime = defaultRuntime) {
|
|
5943
|
+
const modelName = String(model.modelName);
|
|
5944
|
+
runtime.registerModelInstance(modelName, model);
|
|
5945
|
+
return new _ModelRouter(modelName, initialOptions, runtime);
|
|
5946
|
+
}
|
|
5065
5947
|
getRequestSchema(key) {
|
|
5066
5948
|
return this.runtime.getExactModelOption(
|
|
5067
5949
|
this.modelName,
|
|
@@ -5125,13 +6007,13 @@ var ModelRouter = class {
|
|
|
5125
6007
|
}
|
|
5126
6008
|
logEndpoints() {
|
|
5127
6009
|
runWithRuntime(this.runtime, () => {
|
|
5128
|
-
forEach8(this.router.
|
|
5129
|
-
|
|
6010
|
+
forEach8(this.router.getEndpoints(), ({ method, path }) => {
|
|
6011
|
+
logInfoMessage(`${padEnd(method, 6)} ${normalizeUrlPath2(this.options.parentPath + path)}`);
|
|
5130
6012
|
});
|
|
5131
6013
|
});
|
|
5132
6014
|
}
|
|
5133
6015
|
set(keyOrOptions, value) {
|
|
5134
|
-
if (arguments.length === 2 &&
|
|
6016
|
+
if (arguments.length === 2 && isString8(keyOrOptions)) {
|
|
5135
6017
|
assertMutableRouterOption("model", keyOrOptions);
|
|
5136
6018
|
this.runtime.setModelOption(
|
|
5137
6019
|
this.modelName,
|
|
@@ -5139,7 +6021,7 @@ var ModelRouter = class {
|
|
|
5139
6021
|
value
|
|
5140
6022
|
);
|
|
5141
6023
|
}
|
|
5142
|
-
if (arguments.length === 1 &&
|
|
6024
|
+
if (arguments.length === 1 && isPlainObject15(keyOrOptions)) {
|
|
5143
6025
|
assertMutableRouterOptions("model", keyOrOptions);
|
|
5144
6026
|
this.runtime.setModelOptions(this.modelName, keyOrOptions);
|
|
5145
6027
|
}
|
|
@@ -5162,7 +6044,7 @@ var ModelRouter = class {
|
|
|
5162
6044
|
|
|
5163
6045
|
// src/routers/root-router.ts
|
|
5164
6046
|
import JsonRouter6 from "@web-ts-toolkit/express-json-router";
|
|
5165
|
-
import { isNumber as _isNumber,
|
|
6047
|
+
import { isNumber as _isNumber, normalizeUrlPath as normalizeUrlPath3, orderBy as _orderBy } from "@web-ts-toolkit/utils";
|
|
5166
6048
|
|
|
5167
6049
|
// src/core-data.ts
|
|
5168
6050
|
var DataCore = class {
|
|
@@ -5330,16 +6212,19 @@ async function decorateDataSingleResult(svc, result, context = {}) {
|
|
|
5330
6212
|
|
|
5331
6213
|
// src/routers/root-router.ts
|
|
5332
6214
|
var clientErrors3 = JsonRouter6.clientErrors;
|
|
6215
|
+
var DEFAULT_MAX_BATCH_ENTRIES = 100;
|
|
6216
|
+
var DEFAULT_MAX_ORDER_GROUPS = 25;
|
|
6217
|
+
var DEFAULT_MAX_CONCURRENT_OPERATIONS = 10;
|
|
5333
6218
|
var createErrorResult = (code, detail) => ({
|
|
5334
6219
|
success: false,
|
|
6220
|
+
kind: "error",
|
|
5335
6221
|
code,
|
|
5336
6222
|
errors: [{ detail }]
|
|
5337
6223
|
});
|
|
5338
|
-
var normalizeSubPopulate = (populate) => Array.isArray(populate) ? populate.map((item) => isString10(item) ? { path: item } : item) : isString10(populate) ? { path: populate } : populate ?? [];
|
|
5339
6224
|
var RootRouter = class {
|
|
5340
6225
|
constructor(options = { basePath: "", operationAccess: true }, runtime = defaultRuntime) {
|
|
5341
6226
|
this.modelOperations = {
|
|
5342
|
-
new: async (req, item) => req.macl.getPublicService(item.name)._new(),
|
|
6227
|
+
new: async (req, item) => req.macl.getPublicService(item.name)._new(item.args, item.options),
|
|
5343
6228
|
list: async (req, item) => req.macl.getPublicService(item.name)._list(item.filter ?? {}, item.args, item.options),
|
|
5344
6229
|
read: async (req, item) => {
|
|
5345
6230
|
const svc = req.macl.getPublicService(item.name);
|
|
@@ -5362,7 +6247,7 @@ var RootRouter = class {
|
|
|
5362
6247
|
subBulkUpdate: async (req, item) => req.macl.getPublicService(item.name).bulkUpdateSub(item.id, item.sub, item.data),
|
|
5363
6248
|
subDelete: async (req, item) => req.macl.getPublicService(item.name).deleteSub(item.id, item.sub, item.subId),
|
|
5364
6249
|
distinct: async (req, item) => req.macl.getPublicService(item.name)._distinct(item.field, { filter: item.filter }),
|
|
5365
|
-
count: async (req, item) => req.macl.getPublicService(item.name)._count(item.filter ?? {}
|
|
6250
|
+
count: async (req, item) => req.macl.getPublicService(item.name)._count(item.filter ?? {})
|
|
5366
6251
|
};
|
|
5367
6252
|
this.dataOperations = {
|
|
5368
6253
|
list: async (req, item) => {
|
|
@@ -5397,10 +6282,16 @@ var RootRouter = class {
|
|
|
5397
6282
|
return decorateDataSingleResult(svc, result, { dataName: item.name });
|
|
5398
6283
|
}
|
|
5399
6284
|
};
|
|
5400
|
-
const { basePath, operationAccess } = options;
|
|
6285
|
+
const { basePath, operationAccess, maxBatchEntries, maxOrderGroups, maxConcurrentOperations } = options;
|
|
5401
6286
|
this.runtime = runtime;
|
|
5402
6287
|
this.basename = basePath || "";
|
|
5403
6288
|
this.operationAccess = operationAccess;
|
|
6289
|
+
this.maxBatchEntries = this.normalizePositiveLimit(maxBatchEntries, DEFAULT_MAX_BATCH_ENTRIES);
|
|
6290
|
+
this.maxOrderGroups = this.normalizePositiveLimit(maxOrderGroups, DEFAULT_MAX_ORDER_GROUPS);
|
|
6291
|
+
this.maxConcurrentOperations = this.normalizePositiveLimit(
|
|
6292
|
+
maxConcurrentOperations,
|
|
6293
|
+
DEFAULT_MAX_CONCURRENT_OPERATIONS
|
|
6294
|
+
);
|
|
5404
6295
|
this.router = new JsonRouter6(
|
|
5405
6296
|
this.basename,
|
|
5406
6297
|
[createSetCore(this.runtime), createSetDataCore(this.runtime)],
|
|
@@ -5417,7 +6308,7 @@ var RootRouter = class {
|
|
|
5417
6308
|
target: item.target,
|
|
5418
6309
|
name: item.name,
|
|
5419
6310
|
op: item.op,
|
|
5420
|
-
result,
|
|
6311
|
+
result: toPublicServiceResult(result),
|
|
5421
6312
|
message: mapCodeToMessage(result.code),
|
|
5422
6313
|
statusCode: mapCodeToStatusCode(result.code)
|
|
5423
6314
|
};
|
|
@@ -5468,17 +6359,63 @@ var RootRouter = class {
|
|
|
5468
6359
|
return this.wrapResult(index, item, await handler(req, item));
|
|
5469
6360
|
}
|
|
5470
6361
|
groupItemsByOrder(items) {
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
6362
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6363
|
+
items.forEach((item, index) => {
|
|
6364
|
+
const order = _isNumber(item.order) ? item.order : 0;
|
|
6365
|
+
const group = groups.get(order);
|
|
6366
|
+
if (group) {
|
|
6367
|
+
group.push({ index, item });
|
|
6368
|
+
return;
|
|
5475
6369
|
}
|
|
5476
|
-
|
|
5477
|
-
|
|
6370
|
+
groups.set(order, [{ index, item }]);
|
|
6371
|
+
});
|
|
6372
|
+
return Array.from(groups.entries()).sort(([leftOrder], [rightOrder]) => leftOrder - rightOrder).map(([, group]) => group);
|
|
6373
|
+
}
|
|
6374
|
+
normalizePositiveLimit(value, fallback) {
|
|
6375
|
+
return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : fallback;
|
|
6376
|
+
}
|
|
6377
|
+
validateBatch(items) {
|
|
6378
|
+
if (items.length > this.maxBatchEntries) {
|
|
6379
|
+
throw new clientErrors3.BadRequestError("Bad Request", {
|
|
6380
|
+
errors: [{ detail: `Root batch exceeds maximum size of ${this.maxBatchEntries}` }]
|
|
6381
|
+
});
|
|
6382
|
+
}
|
|
6383
|
+
const orders = /* @__PURE__ */ new Set();
|
|
6384
|
+
for (const item of items) {
|
|
6385
|
+
const order = item.order ?? 0;
|
|
6386
|
+
if (!Number.isSafeInteger(order) || order < 0) {
|
|
6387
|
+
throw new clientErrors3.BadRequestError("Bad Request", {
|
|
6388
|
+
errors: [{ detail: "Root batch order must be a non-negative safe integer" }]
|
|
6389
|
+
});
|
|
5478
6390
|
}
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
6391
|
+
if (order > this.maxOrderGroups) {
|
|
6392
|
+
throw new clientErrors3.BadRequestError("Bad Request", {
|
|
6393
|
+
errors: [{ detail: `Root batch order exceeds maximum group index of ${this.maxOrderGroups}` }]
|
|
6394
|
+
});
|
|
6395
|
+
}
|
|
6396
|
+
orders.add(order);
|
|
6397
|
+
}
|
|
6398
|
+
if (orders.size > this.maxOrderGroups) {
|
|
6399
|
+
throw new clientErrors3.BadRequestError("Bad Request", {
|
|
6400
|
+
errors: [{ detail: `Root batch exceeds maximum order groups of ${this.maxOrderGroups}` }]
|
|
6401
|
+
});
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
async runGroup(req, entries) {
|
|
6405
|
+
const results = new Array(entries.length);
|
|
6406
|
+
let cursor = 0;
|
|
6407
|
+
const workerCount = Math.min(this.maxConcurrentOperations, entries.length);
|
|
6408
|
+
await Promise.all(
|
|
6409
|
+
Array.from({ length: workerCount }, async () => {
|
|
6410
|
+
while (cursor < entries.length) {
|
|
6411
|
+
const current = cursor;
|
|
6412
|
+
cursor += 1;
|
|
6413
|
+
const { item, index } = entries[current];
|
|
6414
|
+
results[current] = await this.processOp(req, item, index);
|
|
6415
|
+
}
|
|
6416
|
+
})
|
|
6417
|
+
);
|
|
6418
|
+
return results;
|
|
5482
6419
|
}
|
|
5483
6420
|
async assertAllowed(req) {
|
|
5484
6421
|
const allowed = await req.macl.canActivate(this.operationAccess);
|
|
@@ -5488,10 +6425,11 @@ var RootRouter = class {
|
|
|
5488
6425
|
this.router.post("", async (req) => {
|
|
5489
6426
|
await this.assertAllowed(req);
|
|
5490
6427
|
const items = parseBody(rootQuerySchema, req.body);
|
|
6428
|
+
this.validateBatch(items);
|
|
5491
6429
|
const groupedItems = this.groupItemsByOrder(items);
|
|
5492
6430
|
const results = [];
|
|
5493
6431
|
for (let x = 0; x < groupedItems.length; x++) {
|
|
5494
|
-
const arrResult = await
|
|
6432
|
+
const arrResult = await this.runGroup(req, groupedItems[x]);
|
|
5495
6433
|
results.push(...arrResult);
|
|
5496
6434
|
}
|
|
5497
6435
|
return _orderBy(results, ["index"], ["asc"]);
|
|
@@ -5501,7 +6439,8 @@ var RootRouter = class {
|
|
|
5501
6439
|
path: "",
|
|
5502
6440
|
operationId: "root.query",
|
|
5503
6441
|
summary: "Execute batched model and data operations",
|
|
5504
|
-
body: rootQuerySchema
|
|
6442
|
+
body: rootQuerySchema,
|
|
6443
|
+
idempotent: true
|
|
5505
6444
|
});
|
|
5506
6445
|
}
|
|
5507
6446
|
get routes() {
|
|
@@ -5511,7 +6450,7 @@ var RootRouter = class {
|
|
|
5511
6450
|
|
|
5512
6451
|
// src/routers/data-router.ts
|
|
5513
6452
|
import JsonRouter7 from "@web-ts-toolkit/express-json-router";
|
|
5514
|
-
import { isPlainObject as
|
|
6453
|
+
import { isPlainObject as isPlainObject16, isString as isString9, isUndefined as isUndefined3, normalizeUrlPath as normalizeUrlPath4 } from "@web-ts-toolkit/utils";
|
|
5515
6454
|
var clientErrors4 = JsonRouter7.clientErrors;
|
|
5516
6455
|
function setOption2(parentKey, optionKey, option) {
|
|
5517
6456
|
const key = isUndefined3(option) ? parentKey : `${parentKey}.${optionKey}`;
|
|
@@ -5648,7 +6587,7 @@ var DataRouter = class {
|
|
|
5648
6587
|
const result = await svc.findById(id, {}, {});
|
|
5649
6588
|
handleResultError(result);
|
|
5650
6589
|
const decoratedResult = await decorateDataSingleResult(svc, result);
|
|
5651
|
-
return decoratedResult
|
|
6590
|
+
return unwrapServiceData(decoratedResult);
|
|
5652
6591
|
});
|
|
5653
6592
|
this.registerOpenApiRoute({
|
|
5654
6593
|
method: "get",
|
|
@@ -5667,7 +6606,7 @@ var DataRouter = class {
|
|
|
5667
6606
|
const result = await svc.findOne(filter2 ?? {}, { select }, {});
|
|
5668
6607
|
handleResultError(result);
|
|
5669
6608
|
const decoratedResult = await decorateDataSingleResult(svc, result);
|
|
5670
|
-
return decoratedResult
|
|
6609
|
+
return unwrapServiceData(decoratedResult);
|
|
5671
6610
|
});
|
|
5672
6611
|
this.registerOpenApiRoute({
|
|
5673
6612
|
method: "post",
|
|
@@ -5690,7 +6629,7 @@ var DataRouter = class {
|
|
|
5690
6629
|
const result = await svc.findById(id, { select }, {});
|
|
5691
6630
|
handleResultError(result);
|
|
5692
6631
|
const decoratedResult = await decorateDataSingleResult(svc, result);
|
|
5693
|
-
return decoratedResult
|
|
6632
|
+
return unwrapServiceData(decoratedResult);
|
|
5694
6633
|
});
|
|
5695
6634
|
this.registerOpenApiRoute({
|
|
5696
6635
|
method: "post",
|
|
@@ -5703,11 +6642,11 @@ var DataRouter = class {
|
|
|
5703
6642
|
});
|
|
5704
6643
|
}
|
|
5705
6644
|
set(keyOrOptions, value) {
|
|
5706
|
-
if (arguments.length === 2 &&
|
|
6645
|
+
if (arguments.length === 2 && isString9(keyOrOptions)) {
|
|
5707
6646
|
assertMutableRouterOption("data", keyOrOptions);
|
|
5708
6647
|
this.runtime.setDataOption(this.dataName, keyOrOptions, value);
|
|
5709
6648
|
}
|
|
5710
|
-
if (arguments.length === 1 &&
|
|
6649
|
+
if (arguments.length === 1 && isPlainObject16(keyOrOptions)) {
|
|
5711
6650
|
assertMutableRouterOptions("data", keyOrOptions);
|
|
5712
6651
|
this.runtime.setDataOptions(this.dataName, keyOrOptions);
|
|
5713
6652
|
}
|
|
@@ -5735,6 +6674,9 @@ import JsonRouter8 from "@web-ts-toolkit/express-json-router";
|
|
|
5735
6674
|
import express from "express";
|
|
5736
6675
|
import { posix } from "path";
|
|
5737
6676
|
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6677
|
+
var escapeInlineScriptJson = (value) => {
|
|
6678
|
+
return value.replace(/<\/script/gi, "<\\/script").replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
6679
|
+
};
|
|
5738
6680
|
var renderDocsHtml = (specPath, title, cssUrl, bundleUrl) => `<!doctype html>
|
|
5739
6681
|
<html lang="en">
|
|
5740
6682
|
<head>
|
|
@@ -5748,7 +6690,7 @@ var renderDocsHtml = (specPath, title, cssUrl, bundleUrl) => `<!doctype html>
|
|
|
5748
6690
|
<script src="${escapeHtml(bundleUrl)}"></script>
|
|
5749
6691
|
<script>
|
|
5750
6692
|
window.ui = SwaggerUIBundle({
|
|
5751
|
-
url: ${JSON.stringify(specPath)},
|
|
6693
|
+
url: ${escapeInlineScriptJson(JSON.stringify(specPath))},
|
|
5752
6694
|
dom_id: '#swagger-ui',
|
|
5753
6695
|
deepLinking: true,
|
|
5754
6696
|
presets: [SwaggerUIBundle.presets.apis],
|
|
@@ -5803,10 +6745,10 @@ var accessRouterResponseHandler = JsonRouter8.createHandler({
|
|
|
5803
6745
|
errorFormat: JsonRouter8.ErrorFormats.rfc9457,
|
|
5804
6746
|
rfc9457ContentType: "application/problem+json"
|
|
5805
6747
|
});
|
|
5806
|
-
accessRouterResponseHandler.errorMessageProvider = function(
|
|
5807
|
-
const errorLike =
|
|
5808
|
-
|
|
5809
|
-
return errorLike.message || errorLike._message || String(
|
|
6748
|
+
accessRouterResponseHandler.errorMessageProvider = function(error2) {
|
|
6749
|
+
const errorLike = error2;
|
|
6750
|
+
error(error2);
|
|
6751
|
+
return errorLike.message || errorLike._message || String(error2);
|
|
5810
6752
|
};
|
|
5811
6753
|
|
|
5812
6754
|
// src/plugins.ts
|
|
@@ -5819,6 +6761,7 @@ function permissionsPlugin(schema, options) {
|
|
|
5819
6761
|
}
|
|
5820
6762
|
|
|
5821
6763
|
// src/index.ts
|
|
6764
|
+
var isMongooseModel = (value) => typeof value === "function" && value !== null && "modelName" in value && "schema" in value;
|
|
5822
6765
|
function createRuntimeApi(runtime) {
|
|
5823
6766
|
const runtimeMiddleware = function() {
|
|
5824
6767
|
return createSetCore(runtime);
|
|
@@ -5826,8 +6769,18 @@ function createRuntimeApi(runtime) {
|
|
|
5826
6769
|
const wtt2 = runtimeMiddleware;
|
|
5827
6770
|
wtt2.runtime = runtime;
|
|
5828
6771
|
wtt2.createRouter = function(modelName, options) {
|
|
5829
|
-
|
|
5830
|
-
|
|
6772
|
+
if (isUndefined4(options)) {
|
|
6773
|
+
return new RootRouter(modelName, runtime);
|
|
6774
|
+
}
|
|
6775
|
+
if (typeof modelName === "string") {
|
|
6776
|
+
return new ModelRouter(modelName, options, runtime);
|
|
6777
|
+
}
|
|
6778
|
+
if (isMongooseModel(modelName)) {
|
|
6779
|
+
const name = String(modelName.modelName);
|
|
6780
|
+
runtime.registerModelInstance(name, modelName);
|
|
6781
|
+
return ModelRouter.fromModel(modelName, options, runtime);
|
|
6782
|
+
}
|
|
6783
|
+
throw new TypeError(`createRouter: unsupported first argument type: ${typeof modelName}`);
|
|
5831
6784
|
};
|
|
5832
6785
|
wtt2.createDataRouter = function(dataName, options) {
|
|
5833
6786
|
return new DataRouter(dataName, options, runtime);
|
|
@@ -5837,10 +6790,10 @@ function createRuntimeApi(runtime) {
|
|
|
5837
6790
|
};
|
|
5838
6791
|
wtt2.combineRoutes = combineRoutes;
|
|
5839
6792
|
wtt2.set = function(keyOrOptions, value) {
|
|
5840
|
-
if (arguments.length === 2 &&
|
|
6793
|
+
if (arguments.length === 2 && isString10(keyOrOptions)) {
|
|
5841
6794
|
return runtime.setGlobalOption(keyOrOptions, value);
|
|
5842
6795
|
}
|
|
5843
|
-
if (arguments.length === 1 &&
|
|
6796
|
+
if (arguments.length === 1 && isPlainObject17(keyOrOptions)) {
|
|
5844
6797
|
return runtime.setGlobalOptions(keyOrOptions);
|
|
5845
6798
|
}
|
|
5846
6799
|
};
|
|
@@ -5858,6 +6811,9 @@ function createRuntimeApi(runtime) {
|
|
|
5858
6811
|
wtt2.setDefaultModelOption = runtime.setDefaultModelOption.bind(runtime);
|
|
5859
6812
|
wtt2.getDefaultModelOptions = runtime.getDefaultModelOptions.bind(runtime);
|
|
5860
6813
|
wtt2.getDefaultModelOption = runtime.getDefaultModelOption.bind(runtime);
|
|
6814
|
+
wtt2.registerModelInstance = runtime.registerModelInstance.bind(runtime);
|
|
6815
|
+
wtt2.hasModelInstance = runtime.hasModelInstance.bind(runtime);
|
|
6816
|
+
wtt2.getModelInstance = runtime.getModelInstance.bind(runtime);
|
|
5861
6817
|
wtt2.RootRouter = RootRouter;
|
|
5862
6818
|
wtt2.ModelRouter = ModelRouter;
|
|
5863
6819
|
wtt2.DataRouter = DataRouter;
|
|
@@ -5873,12 +6829,15 @@ export {
|
|
|
5873
6829
|
AccessRuntime,
|
|
5874
6830
|
DataRouter,
|
|
5875
6831
|
ModelRouter,
|
|
6832
|
+
OpenApiCollisionError,
|
|
6833
|
+
OpenApiRegistry,
|
|
5876
6834
|
RootRouter,
|
|
5877
6835
|
acl,
|
|
5878
6836
|
combineRoutes,
|
|
5879
6837
|
createAccessRuntime,
|
|
5880
6838
|
createOpenApiRouter,
|
|
5881
6839
|
index_default as default,
|
|
6840
|
+
defaultRuntime,
|
|
5882
6841
|
defineRequestSchema,
|
|
5883
6842
|
fromAjv,
|
|
5884
6843
|
fromArkType,
|
|
@@ -5894,12 +6853,19 @@ export {
|
|
|
5894
6853
|
getDefaultModelOptions,
|
|
5895
6854
|
getGlobalOption,
|
|
5896
6855
|
getGlobalOptions,
|
|
6856
|
+
getModelInstance,
|
|
5897
6857
|
getModelJsonSchema,
|
|
5898
6858
|
getModelNames,
|
|
5899
6859
|
getModelOption,
|
|
5900
6860
|
getModelOptions,
|
|
5901
6861
|
guard,
|
|
6862
|
+
hasModelInstance,
|
|
6863
|
+
isLevelEnabled,
|
|
5902
6864
|
permissionsPlugin,
|
|
6865
|
+
redactFilter,
|
|
6866
|
+
redactPayload,
|
|
6867
|
+
registerModelInstance,
|
|
6868
|
+
safeStringify,
|
|
5903
6869
|
setDefaultModelOption,
|
|
5904
6870
|
setDefaultModelOptions,
|
|
5905
6871
|
setGlobalOption,
|