@web-ts-toolkit/access-router 0.38.0 → 0.39.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 +51 -5
- package/advanced.d.mts +3 -3
- package/advanced.d.ts +3 -3
- package/advanced.js +170 -85
- package/advanced.mjs +144 -59
- package/index.d.mts +198 -3
- package/index.d.ts +198 -3
- package/index.js +519 -306
- package/index.mjs +540 -328
- package/llms.txt +25 -5
- package/package.json +3 -3
- package/{parsers--DZcTstL.d.mts → parsers-PMWujHUV.d.mts} +32 -204
- package/{parsers--DZcTstL.d.ts → parsers-PMWujHUV.d.ts} +32 -204
- package/processors.d.mts +20 -8
- package/processors.d.ts +20 -8
package/index.js
CHANGED
|
@@ -104,12 +104,42 @@ var findElementById = (collection, id) => {
|
|
|
104
104
|
return findElement(collection, { _id: id });
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
+
// src/helpers/concurrency.ts
|
|
108
|
+
var mapWithConcurrencyLimit = async (items, limit, iteratee) => {
|
|
109
|
+
const results = new Array(items.length);
|
|
110
|
+
let cursor = 0;
|
|
111
|
+
const workerCount = Math.min(Math.max(limit, 1), items.length || 1);
|
|
112
|
+
await Promise.all(
|
|
113
|
+
Array.from({ length: workerCount }, async () => {
|
|
114
|
+
while (cursor < items.length) {
|
|
115
|
+
const current = cursor;
|
|
116
|
+
cursor += 1;
|
|
117
|
+
results[current] = await iteratee(items[current], current);
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
return results;
|
|
122
|
+
};
|
|
123
|
+
var RequestConcurrencyScheduler = class {
|
|
124
|
+
constructor(limit) {
|
|
125
|
+
this.limit = Math.max(1, limit);
|
|
126
|
+
}
|
|
127
|
+
async map(items, iteratee) {
|
|
128
|
+
return mapWithConcurrencyLimit(items, this.limit, (item, index) => iteratee(item, index, true));
|
|
129
|
+
}
|
|
130
|
+
async run(iteratee) {
|
|
131
|
+
const [result] = await this.map([void 0], () => iteratee(true));
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
107
136
|
// src/helpers/document.ts
|
|
108
137
|
var import_mongoose = require("mongoose");
|
|
109
138
|
var import_utils3 = require("@web-ts-toolkit/utils");
|
|
110
139
|
|
|
111
140
|
// src/helpers/query.ts
|
|
112
141
|
var import_utils2 = require("@web-ts-toolkit/utils");
|
|
142
|
+
var DEFAULT_LIST_HARD_LIMIT = 1e3;
|
|
113
143
|
var normalizeSafeInteger = (value, min) => {
|
|
114
144
|
if ((0, import_utils2.isNil)(value)) return null;
|
|
115
145
|
const parsed = Number(value);
|
|
@@ -122,9 +152,10 @@ function genPagination({
|
|
|
122
152
|
page,
|
|
123
153
|
pageSize
|
|
124
154
|
}, hardLimit) {
|
|
155
|
+
const resolvedHardLimit = normalizeSafeInteger(hardLimit, 1) ?? DEFAULT_LIST_HARD_LIMIT;
|
|
125
156
|
let _skip = 0;
|
|
126
|
-
let _limit = normalizeSafeInteger(limit ?? pageSize, 1) ??
|
|
127
|
-
if (!Number.isSafeInteger(_limit) || _limit >
|
|
157
|
+
let _limit = normalizeSafeInteger(limit ?? pageSize, 1) ?? resolvedHardLimit;
|
|
158
|
+
if (!Number.isSafeInteger(_limit) || _limit > resolvedHardLimit) _limit = resolvedHardLimit;
|
|
128
159
|
const normalizedSkip = normalizeSafeInteger(skip, 0);
|
|
129
160
|
const normalizedPage = normalizeSafeInteger(page, 0);
|
|
130
161
|
if (normalizedSkip !== null) {
|
|
@@ -141,14 +172,6 @@ function genPagination({
|
|
|
141
172
|
}
|
|
142
173
|
return { skip: _skip, limit: _limit };
|
|
143
174
|
}
|
|
144
|
-
function parseSortString(sortString) {
|
|
145
|
-
if (!sortString) return { sortKey: "", sortOrder: "asc" };
|
|
146
|
-
if (sortString.startsWith("-")) {
|
|
147
|
-
return { sortKey: sortString.substring(1), sortOrder: "desc" };
|
|
148
|
-
} else {
|
|
149
|
-
return { sortKey: sortString, sortOrder: "asc" };
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
175
|
function normalizeSelect(select) {
|
|
153
176
|
if (Array.isArray(select)) return (0, import_utils2.flattenDeep)(select.map(normalizeSelect));
|
|
154
177
|
if ((0, import_utils2.isPlainObject)(select)) {
|
|
@@ -278,6 +301,61 @@ function handleResultError(result) {
|
|
|
278
301
|
}
|
|
279
302
|
}
|
|
280
303
|
|
|
304
|
+
// src/helpers/sort-policy.ts
|
|
305
|
+
var validSortOrders = [1, -1, "asc", "ascending", "desc", "descending"];
|
|
306
|
+
var fieldPathPattern = /^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)*$/;
|
|
307
|
+
var sortDirection = (order) => order === -1 || order === "desc" || order === "descending" ? "desc" : "asc";
|
|
308
|
+
var sortError = (detail) => ({ detail, pointer: "#/sort" });
|
|
309
|
+
function isValidFieldPath(field) {
|
|
310
|
+
return typeof field === "string" && fieldPathPattern.test(field);
|
|
311
|
+
}
|
|
312
|
+
function isFieldAllowed(field, allowedFields) {
|
|
313
|
+
return new Set(allowedFields.concat(["id", "_id"])).has(field);
|
|
314
|
+
}
|
|
315
|
+
function normalizeSort(sort) {
|
|
316
|
+
if (sort === null || sort === void 0 || sort === "") return { fields: [], errors: [] };
|
|
317
|
+
if (typeof sort === "string") {
|
|
318
|
+
const fields2 = sort.trim().split(/\s+/).filter(Boolean).map((raw) => {
|
|
319
|
+
const desc = raw.startsWith("-");
|
|
320
|
+
return { raw, field: desc ? raw.slice(1) : raw, direction: desc ? "desc" : "asc" };
|
|
321
|
+
});
|
|
322
|
+
const errors2 = fields2.filter(({ field }) => !isValidFieldPath(field)).map(({ raw }) => sortError(`Invalid sort field: ${raw}`));
|
|
323
|
+
return { fields: errors2.length > 0 ? [] : fields2.map(({ field, direction }) => ({ field, direction })), errors: errors2 };
|
|
324
|
+
}
|
|
325
|
+
const entries = sort instanceof Map ? Array.from(sort.entries()) : Array.isArray(sort) ? sort : Object.entries(sort);
|
|
326
|
+
const errors = [];
|
|
327
|
+
const fields = [];
|
|
328
|
+
for (const entry of entries) {
|
|
329
|
+
if (!Array.isArray(entry) || entry.length !== 2) {
|
|
330
|
+
errors.push(sortError("Invalid sort entry: expected [field, order]"));
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const [field, order] = entry;
|
|
334
|
+
if (!isValidFieldPath(field)) {
|
|
335
|
+
errors.push(sortError(`Invalid sort field: ${String(field)}`));
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (!validSortOrders.includes(order)) {
|
|
339
|
+
errors.push(sortError(`Invalid sort order for field: ${field}`));
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
fields.push({ field, direction: sortDirection(order) });
|
|
343
|
+
}
|
|
344
|
+
return { fields: errors.length > 0 ? [] : fields, errors };
|
|
345
|
+
}
|
|
346
|
+
function validateSortFields(sort, allowedFields) {
|
|
347
|
+
const { fields, errors } = normalizeSort(sort);
|
|
348
|
+
if (errors.length > 0) return errors;
|
|
349
|
+
return fields.filter(({ field }) => !isFieldAllowed(field, allowedFields)).map(({ field }) => sortError(`Sort field is not allowed: ${field}`));
|
|
350
|
+
}
|
|
351
|
+
function normalizeSortForOrderBy(sort) {
|
|
352
|
+
const normalized = normalizeSort(sort);
|
|
353
|
+
return {
|
|
354
|
+
fields: normalized.fields.map(({ field }) => field),
|
|
355
|
+
orders: normalized.fields.map(({ direction }) => direction)
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
281
359
|
// src/helpers/index.ts
|
|
282
360
|
var import_utils6 = require("@web-ts-toolkit/utils");
|
|
283
361
|
|
|
@@ -339,26 +417,33 @@ function buildSubPaths(schema) {
|
|
|
339
417
|
});
|
|
340
418
|
return subPaths;
|
|
341
419
|
}
|
|
342
|
-
async function iterateQuery(query, handler) {
|
|
420
|
+
async function iterateQuery(query, handler, scheduler, scheduled = false) {
|
|
343
421
|
if (!(0, import_utils6.isPlainObject)(query)) return query;
|
|
344
422
|
if (!handler) return query;
|
|
345
423
|
const queryObject = query;
|
|
346
|
-
|
|
424
|
+
const entries = Object.entries(queryObject);
|
|
425
|
+
const mapEntry = async ([key, val]) => {
|
|
347
426
|
if ((0, import_utils6.isPlainObject)(val)) {
|
|
348
427
|
const plainValue = val;
|
|
349
428
|
if (plainValue.$$sq) {
|
|
350
|
-
return handler(0 /* SubQuery */, plainValue.$$sq, key);
|
|
429
|
+
return scheduled || !scheduler ? handler(0 /* SubQuery */, plainValue.$$sq, key) : scheduler.run(() => Promise.resolve(handler(0 /* SubQuery */, plainValue.$$sq, key)));
|
|
351
430
|
} else if (plainValue.$$date) {
|
|
352
431
|
return handler(1 /* Date */, plainValue.$$date, key);
|
|
353
432
|
} else {
|
|
354
|
-
return iterateQuery(val, handler);
|
|
433
|
+
return iterateQuery(val, handler, scheduler, scheduled);
|
|
355
434
|
}
|
|
356
435
|
}
|
|
357
436
|
if ((0, import_utils6.isArray)(val)) {
|
|
358
|
-
|
|
437
|
+
const items = val;
|
|
438
|
+
if (scheduler && !scheduled) {
|
|
439
|
+
return scheduler.map(items, (v) => iterateQuery(v, handler, scheduler, true));
|
|
440
|
+
}
|
|
441
|
+
return Promise.all(items.map((v) => iterateQuery(v, handler, scheduler, scheduled)));
|
|
359
442
|
}
|
|
360
443
|
return val;
|
|
361
|
-
}
|
|
444
|
+
};
|
|
445
|
+
const values = scheduler && !scheduled ? await scheduler.map(entries, mapEntry) : await Promise.all(entries.map(mapEntry));
|
|
446
|
+
return Object.fromEntries(entries.map(([key], index) => [key, values[index]]));
|
|
362
447
|
}
|
|
363
448
|
var createValidator = (fn) => {
|
|
364
449
|
const stringHandler = (key) => key.trim().split(" ").every((v) => fn(v));
|
|
@@ -724,6 +809,42 @@ var defaultLogger = (0, import_winston.createLogger)({
|
|
|
724
809
|
|
|
725
810
|
// src/options/manager.ts
|
|
726
811
|
var import_utils9 = require("@web-ts-toolkit/utils");
|
|
812
|
+
var toPath = (key) => {
|
|
813
|
+
if (Array.isArray(key)) return key;
|
|
814
|
+
return typeof key === "string" ? key.split(".") : [key];
|
|
815
|
+
};
|
|
816
|
+
var cloneConfigValue = (value) => {
|
|
817
|
+
if (Array.isArray(value)) {
|
|
818
|
+
return value.map((item) => cloneConfigValue(item));
|
|
819
|
+
}
|
|
820
|
+
if (!value || typeof value !== "object") {
|
|
821
|
+
return value;
|
|
822
|
+
}
|
|
823
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) {
|
|
824
|
+
return value;
|
|
825
|
+
}
|
|
826
|
+
return Object.fromEntries(
|
|
827
|
+
Object.entries(value).map(([entryKey, entryValue]) => [
|
|
828
|
+
entryKey,
|
|
829
|
+
cloneConfigValue(entryValue)
|
|
830
|
+
])
|
|
831
|
+
);
|
|
832
|
+
};
|
|
833
|
+
var freezeConfigValue = (value) => {
|
|
834
|
+
if (!value || typeof value !== "object") {
|
|
835
|
+
return value;
|
|
836
|
+
}
|
|
837
|
+
if (Object.isFrozen(value)) {
|
|
838
|
+
return value;
|
|
839
|
+
}
|
|
840
|
+
if (Array.isArray(value) || Object.getPrototypeOf(value) === Object.prototype) {
|
|
841
|
+
for (const nestedValue of Object.values(value)) {
|
|
842
|
+
freezeConfigValue(nestedValue);
|
|
843
|
+
}
|
|
844
|
+
return Object.freeze(value);
|
|
845
|
+
}
|
|
846
|
+
return value;
|
|
847
|
+
};
|
|
727
848
|
var getNestedOption = (manager, key, defaultValue) => {
|
|
728
849
|
const keys2 = String(key).split(".");
|
|
729
850
|
if (keys2.length === 1) {
|
|
@@ -741,8 +862,9 @@ var getNestedOption = (manager, key, defaultValue) => {
|
|
|
741
862
|
return option;
|
|
742
863
|
};
|
|
743
864
|
var OptionsManager = class {
|
|
744
|
-
constructor(defaultOptions) {
|
|
865
|
+
constructor(defaultOptions, options = {}) {
|
|
745
866
|
this.defaultOptions = defaultOptions;
|
|
867
|
+
this.preserveKeys = new Set(options.preserveKeys ?? []);
|
|
746
868
|
this.listeners = {};
|
|
747
869
|
const _this = this;
|
|
748
870
|
this.currentOptions = new Proxy({}, {
|
|
@@ -763,13 +885,34 @@ var OptionsManager = class {
|
|
|
763
885
|
return (0, import_utils9.get)(this.currentOptions, key, defaultValue);
|
|
764
886
|
}
|
|
765
887
|
set(key, value) {
|
|
766
|
-
|
|
888
|
+
const path = toPath(key);
|
|
889
|
+
if (path.length <= 1) {
|
|
890
|
+
(0, import_utils9.set)(this.currentOptions, path, this.preserveKeys.has(String(path[0])) ? value : cloneConfigValue(value));
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const [rootKey, ...nestedPath] = path;
|
|
894
|
+
const currentRoot = (0, import_utils9.get)(this.currentOptions, [rootKey], void 0);
|
|
895
|
+
const nextRoot = cloneConfigValue(currentRoot ?? {});
|
|
896
|
+
(0, import_utils9.set)(nextRoot, nestedPath, cloneConfigValue(value));
|
|
897
|
+
(0, import_utils9.set)(this.currentOptions, [rootKey], nextRoot);
|
|
767
898
|
}
|
|
768
899
|
fetch() {
|
|
769
|
-
|
|
900
|
+
const cloned = cloneConfigValue(this.currentOptions);
|
|
901
|
+
for (const key of this.preserveKeys) {
|
|
902
|
+
if (key in this.currentOptions) {
|
|
903
|
+
cloned[key] = this.currentOptions[key];
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return freezeConfigValue(cloned);
|
|
770
907
|
}
|
|
771
908
|
assign(options) {
|
|
772
|
-
|
|
909
|
+
const cloned = cloneConfigValue(options);
|
|
910
|
+
for (const key of this.preserveKeys) {
|
|
911
|
+
if (key in options) {
|
|
912
|
+
cloned[key] = options[key];
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
(0, import_utils9.assign)(this.currentOptions, cloned);
|
|
773
916
|
}
|
|
774
917
|
onchange(key, func) {
|
|
775
918
|
(0, import_utils9.set)(this.listeners, key, func);
|
|
@@ -787,7 +930,8 @@ var defaultRequestComplexity = {
|
|
|
787
930
|
maxBulkItems: 100,
|
|
788
931
|
maxIncludeCount: 10,
|
|
789
932
|
maxSubQueryCount: 10,
|
|
790
|
-
maxBulkConcurrency: 10
|
|
933
|
+
maxBulkConcurrency: 10,
|
|
934
|
+
maxHookConcurrency: 10
|
|
791
935
|
};
|
|
792
936
|
var normalizePositiveInteger = (value, fallback) => {
|
|
793
937
|
return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : fallback;
|
|
@@ -803,6 +947,10 @@ var resolveRequestComplexity = (options) => ({
|
|
|
803
947
|
maxBulkConcurrency: normalizePositiveInteger(
|
|
804
948
|
options?.maxBulkConcurrency,
|
|
805
949
|
defaultRequestComplexity.maxBulkConcurrency
|
|
950
|
+
),
|
|
951
|
+
maxHookConcurrency: normalizePositiveInteger(
|
|
952
|
+
options?.maxHookConcurrency,
|
|
953
|
+
defaultRequestComplexity.maxHookConcurrency
|
|
806
954
|
)
|
|
807
955
|
});
|
|
808
956
|
function validateRequestComplexity(value, options, scope = "request") {
|
|
@@ -896,7 +1044,8 @@ var defaultModelOptions = {
|
|
|
896
1044
|
var defaultDataOptions = {
|
|
897
1045
|
basePath: null,
|
|
898
1046
|
parentPath: "/",
|
|
899
|
-
queryRouteSegment: "__query"
|
|
1047
|
+
queryRouteSegment: "__query",
|
|
1048
|
+
listHardLimit: DEFAULT_LIST_HARD_LIMIT
|
|
900
1049
|
};
|
|
901
1050
|
var normalizeBasePath = (name, value) => {
|
|
902
1051
|
if ((0, import_utils11.isNil)(value)) {
|
|
@@ -953,17 +1102,30 @@ var classifyPermissionSchema = (permissionSchema, modelPermissionPrefix) => {
|
|
|
953
1102
|
modelPermissionKeys
|
|
954
1103
|
};
|
|
955
1104
|
};
|
|
1105
|
+
var refreshPermissionMetadata = (target) => {
|
|
1106
|
+
const permissionSchema = target.permissionSchema ?? {};
|
|
1107
|
+
const { schemaKeys, globalPermissionKeys, modelPermissionKeys } = classifyPermissionSchema(
|
|
1108
|
+
permissionSchema,
|
|
1109
|
+
target.modelPermissionPrefix ?? ""
|
|
1110
|
+
);
|
|
1111
|
+
target._permissionSchemaKeys = schemaKeys;
|
|
1112
|
+
target._globalPermissionKeys = globalPermissionKeys;
|
|
1113
|
+
target._modelPermissionKeys = modelPermissionKeys;
|
|
1114
|
+
};
|
|
956
1115
|
var AccessRuntime = class {
|
|
957
|
-
constructor() {
|
|
958
|
-
this.globalOptions = new OptionsManager(
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1116
|
+
constructor(options = {}) {
|
|
1117
|
+
this.globalOptions = new OptionsManager(
|
|
1118
|
+
{
|
|
1119
|
+
requestPermissionField: "_permissions",
|
|
1120
|
+
globalPermissions: () => ({}),
|
|
1121
|
+
requireRegisteredPopulateModels: true,
|
|
1122
|
+
logger: defaultLogger,
|
|
1123
|
+
requestComplexity: defaultRequestComplexity
|
|
1124
|
+
},
|
|
1125
|
+
{ preserveKeys: ["logger"] }
|
|
1126
|
+
).build();
|
|
965
1127
|
this.defaultModelOptions = new OptionsManager({
|
|
966
|
-
listHardLimit:
|
|
1128
|
+
listHardLimit: DEFAULT_LIST_HARD_LIMIT,
|
|
967
1129
|
documentPermissionField: "_permissions",
|
|
968
1130
|
idParam: "id",
|
|
969
1131
|
idField: "_id",
|
|
@@ -981,6 +1143,7 @@ var AccessRuntime = class {
|
|
|
981
1143
|
this.modelAtts = {};
|
|
982
1144
|
this.modelInstances = {};
|
|
983
1145
|
this.openApiRegistry = new OpenApiRegistry();
|
|
1146
|
+
this.allowGlobalModelLookup = options.allowGlobalModelLookup ?? false;
|
|
984
1147
|
ensureMongooseJsonSchemaInitialized();
|
|
985
1148
|
}
|
|
986
1149
|
registerModelInstance(modelName, model) {
|
|
@@ -996,13 +1159,16 @@ var AccessRuntime = class {
|
|
|
996
1159
|
this.modelInstances[modelName] = model;
|
|
997
1160
|
}
|
|
998
1161
|
hasModelInstance(modelName) {
|
|
999
|
-
return modelName in this.modelInstances;
|
|
1162
|
+
return modelName in this.modelInstances || this.allowGlobalModelLookup && modelName in import_mongoose3.default.models;
|
|
1000
1163
|
}
|
|
1001
1164
|
getModelInstance(modelName) {
|
|
1002
1165
|
const registered = this.modelInstances[modelName];
|
|
1003
1166
|
if (registered) return registered;
|
|
1167
|
+
if (!this.allowGlobalModelLookup) return null;
|
|
1004
1168
|
const global = import_mongoose3.default.models[modelName];
|
|
1005
|
-
|
|
1169
|
+
if (!global) return null;
|
|
1170
|
+
this.registerModelInstance(modelName, global);
|
|
1171
|
+
return global;
|
|
1006
1172
|
}
|
|
1007
1173
|
registerOpenApiRoute(route) {
|
|
1008
1174
|
this.openApiRegistry.register(route);
|
|
@@ -1051,20 +1217,20 @@ var AccessRuntime = class {
|
|
|
1051
1217
|
return this.defaultModelOptions.get(key, defaultValue);
|
|
1052
1218
|
}
|
|
1053
1219
|
createModelOptions(modelName) {
|
|
1054
|
-
const model = this.getModelInstance(modelName)
|
|
1220
|
+
const model = this.getModelInstance(modelName);
|
|
1221
|
+
if (!model) {
|
|
1222
|
+
throw new Error(
|
|
1223
|
+
`Runtime model registry missing model "${modelName}". Pass a mongoose.Model instance to createRouter() or register it with registerModelInstance() before using this isolated runtime.`
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1055
1226
|
const manager = new OptionsManager({
|
|
1056
1227
|
...defaultModelOptions,
|
|
1057
1228
|
modelName
|
|
1058
1229
|
});
|
|
1059
|
-
manager.onchange("permissionSchema", function(
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
target.modelPermissionPrefix ?? ""
|
|
1064
|
-
);
|
|
1065
|
-
target._permissionSchemaKeys = schemaKeys;
|
|
1066
|
-
target._globalPermissionKeys = globalPermissionKeys;
|
|
1067
|
-
target._modelPermissionKeys = modelPermissionKeys;
|
|
1230
|
+
manager.onchange("permissionSchema", function(_newval, _key, target) {
|
|
1231
|
+
refreshPermissionMetadata(target);
|
|
1232
|
+
}).onchange("modelPermissionPrefix", function(_newval, _key, target) {
|
|
1233
|
+
refreshPermissionMetadata(target);
|
|
1068
1234
|
}).onchange("basePath", function(newval, key, target) {
|
|
1069
1235
|
target[key] = normalizeBasePath(
|
|
1070
1236
|
modelName,
|
|
@@ -1193,7 +1359,7 @@ var AccessRuntime = class {
|
|
|
1193
1359
|
return (0, import_utils11.get)(this.modelAtts, modelName, []);
|
|
1194
1360
|
}
|
|
1195
1361
|
};
|
|
1196
|
-
var defaultRuntime = new AccessRuntime();
|
|
1362
|
+
var defaultRuntime = new AccessRuntime({ allowGlobalModelLookup: true });
|
|
1197
1363
|
|
|
1198
1364
|
// src/runtime-context.ts
|
|
1199
1365
|
var import_node_async_hooks = require("async_hooks");
|
|
@@ -1345,8 +1511,8 @@ var Base = class {
|
|
|
1345
1511
|
genIDFilter(id) {
|
|
1346
1512
|
return this.req.macl.genIDFilter(this.modelName, id);
|
|
1347
1513
|
}
|
|
1348
|
-
genPopulate(access, populate) {
|
|
1349
|
-
return this.req.macl.genPopulate(this.modelName, access, populate);
|
|
1514
|
+
genPopulate(access, populate, subPaths) {
|
|
1515
|
+
return this.req.macl.genPopulate(this.modelName, access, populate, subPaths);
|
|
1350
1516
|
}
|
|
1351
1517
|
genSelect(access, targetFields, skipChecks, subPaths) {
|
|
1352
1518
|
return this.req.macl.genSelect(this.modelName, access, targetFields, skipChecks, subPaths);
|
|
@@ -1443,6 +1609,55 @@ var Base = class {
|
|
|
1443
1609
|
includePaths
|
|
1444
1610
|
};
|
|
1445
1611
|
}
|
|
1612
|
+
getForeignKeyValues(value) {
|
|
1613
|
+
return (0, import_utils12.flatten)((0, import_utils12.castArray)(value));
|
|
1614
|
+
}
|
|
1615
|
+
getForeignKey(value) {
|
|
1616
|
+
return String(value);
|
|
1617
|
+
}
|
|
1618
|
+
buildForeignKeyIndex(rows, foreignField) {
|
|
1619
|
+
const index = /* @__PURE__ */ new Map();
|
|
1620
|
+
for (const row of rows) {
|
|
1621
|
+
for (const value of this.getForeignKeyValues((0, import_utils12.get)(row, foreignField))) {
|
|
1622
|
+
const key = this.getForeignKey(value);
|
|
1623
|
+
const values = index.get(key);
|
|
1624
|
+
if (values) values.push(row);
|
|
1625
|
+
else index.set(key, [row]);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
return index;
|
|
1629
|
+
}
|
|
1630
|
+
getIndexedMatches(index, localValue) {
|
|
1631
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1632
|
+
const matches = [];
|
|
1633
|
+
for (const value of this.getForeignKeyValues(localValue)) {
|
|
1634
|
+
for (const row of index.get(this.getForeignKey(value)) ?? []) {
|
|
1635
|
+
if (seen.has(row)) continue;
|
|
1636
|
+
seen.add(row);
|
|
1637
|
+
matches.push(row);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return matches;
|
|
1641
|
+
}
|
|
1642
|
+
getIndexedCount(index, localValue) {
|
|
1643
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1644
|
+
for (const value of this.getForeignKeyValues(localValue)) {
|
|
1645
|
+
for (const id of index.get(this.getForeignKey(value)) ?? []) {
|
|
1646
|
+
ids.add(id);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
return ids.size;
|
|
1650
|
+
}
|
|
1651
|
+
sanitizeIncludeArgs(args) {
|
|
1652
|
+
if (!(0, import_utils12.isPlainObject)(args)) return {};
|
|
1653
|
+
const { overrides: _clientOverrides, ...trustedArgs } = args;
|
|
1654
|
+
return trustedArgs;
|
|
1655
|
+
}
|
|
1656
|
+
assertIncludeForeignField(include) {
|
|
1657
|
+
if (!isValidFieldPath(include.foreignField)) {
|
|
1658
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, `Invalid include foreignField: ${include.foreignField}`);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1446
1661
|
async includeDocs(docs, include) {
|
|
1447
1662
|
if (!include) return docs;
|
|
1448
1663
|
const includes = (0, import_utils12.compact)((0, import_utils12.castArray)(include));
|
|
@@ -1451,25 +1666,52 @@ var Base = class {
|
|
|
1451
1666
|
let docList = isSingle ? [docs] : docs;
|
|
1452
1667
|
for (let x = 0; x < includes.length; x++) {
|
|
1453
1668
|
const include2 = includes[x];
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1669
|
+
this.assertIncludeForeignField(include2);
|
|
1670
|
+
switch (include2.op) {
|
|
1671
|
+
case "count":
|
|
1672
|
+
docList = await this.includeDocsCount(docList, include2);
|
|
1673
|
+
break;
|
|
1674
|
+
case "read":
|
|
1675
|
+
docList = await this.includeDocsRead(docList, include2);
|
|
1676
|
+
break;
|
|
1677
|
+
case "list":
|
|
1678
|
+
docList = await this.includeDocsList(docList, include2);
|
|
1679
|
+
break;
|
|
1458
1680
|
}
|
|
1459
1681
|
}
|
|
1460
1682
|
return isSingle ? docList[0] : docList;
|
|
1461
1683
|
}
|
|
1684
|
+
async includeDocsRead(docs, include) {
|
|
1685
|
+
const { model, path, localField, foreignField, filter: _filters, args = {}, options = {} } = include;
|
|
1686
|
+
const svc = await this.getAuthorizedTargetService(model, "read");
|
|
1687
|
+
for (let y = 0; y < docs.length; y++) {
|
|
1688
|
+
const doc = docs[y];
|
|
1689
|
+
const localValue = (0, import_utils12.get)(doc, localField);
|
|
1690
|
+
const filter2 = { ..._filters ?? {}, [foreignField]: { $in: this.getForeignKeyValues(localValue) } };
|
|
1691
|
+
const trustedOptions = {
|
|
1692
|
+
...options,
|
|
1693
|
+
access: "read",
|
|
1694
|
+
lean: true,
|
|
1695
|
+
includePermissions: false
|
|
1696
|
+
};
|
|
1697
|
+
const result = await svc.findOne(filter2, this.sanitizeIncludeArgs(args), trustedOptions);
|
|
1698
|
+
if (result.success) {
|
|
1699
|
+
setDocValue(doc, path, result.data);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
return docs;
|
|
1703
|
+
}
|
|
1462
1704
|
async includeDocsList(docs, include) {
|
|
1463
1705
|
const { model, op, path, localField, foreignField, filter: _filters, args = {}, options = {} } = include;
|
|
1464
1706
|
const svc = await this.getAuthorizedTargetService(model, op);
|
|
1465
1707
|
const includeLocalValues = [];
|
|
1466
|
-
(0, import_utils12.forEach)(docs, (doc
|
|
1467
|
-
includeLocalValues.push((0, import_utils12.get)(doc, localField));
|
|
1708
|
+
(0, import_utils12.forEach)(docs, (doc) => {
|
|
1709
|
+
includeLocalValues.push(...this.getForeignKeyValues((0, import_utils12.get)(doc, localField)));
|
|
1468
1710
|
});
|
|
1469
1711
|
const filter2 = { ..._filters ?? {}, [foreignField]: { $in: (0, import_utils12.flatten)(includeLocalValues) } };
|
|
1470
1712
|
const authorizedFilter = await svc.genFilter(op, filter2);
|
|
1471
1713
|
const trustedArgs = {
|
|
1472
|
-
...args,
|
|
1714
|
+
...this.sanitizeIncludeArgs(args),
|
|
1473
1715
|
overrides: {
|
|
1474
1716
|
filter: authorizedFilter
|
|
1475
1717
|
}
|
|
@@ -1482,58 +1724,42 @@ var Base = class {
|
|
|
1482
1724
|
};
|
|
1483
1725
|
const trustedResult = await svc.find(filter2, trustedArgs, trustedOptions);
|
|
1484
1726
|
if (!trustedResult.success) return docs;
|
|
1727
|
+
const index = this.buildForeignKeyIndex(trustedResult.data, foreignField);
|
|
1485
1728
|
for (let y = 0; y < docs.length; y++) {
|
|
1486
1729
|
const doc = docs[y];
|
|
1487
|
-
const
|
|
1488
|
-
const filterFn = (row) => (0, import_utils12.intersectionBy)((0, import_utils12.castArray)(localValue), (0, import_utils12.castArray)((0, import_utils12.get)(row, foreignField)), String).length > 0;
|
|
1489
|
-
const matches = trustedResult.data.filter(filterFn);
|
|
1730
|
+
const matches = this.getIndexedMatches(index, (0, import_utils12.get)(doc, localField));
|
|
1490
1731
|
setDocValue(doc, path, op === "list" ? matches : matches[0]);
|
|
1491
1732
|
}
|
|
1492
1733
|
return docs;
|
|
1493
1734
|
}
|
|
1494
1735
|
async includeDocsCount(docs, include) {
|
|
1495
|
-
const { model, path, localField, foreignField, filter: _filters
|
|
1736
|
+
const { model, path, localField, foreignField, filter: _filters } = include;
|
|
1496
1737
|
const svc = await this.getAuthorizedTargetService(model, "count");
|
|
1497
|
-
const
|
|
1498
|
-
|
|
1499
|
-
includeLocalValues.push((0, import_utils12.get)(doc, localField));
|
|
1500
|
-
});
|
|
1501
|
-
const filter2 = { ..._filters ?? {}, [foreignField]: { $in: (0, import_utils12.flatten)(includeLocalValues) } };
|
|
1502
|
-
const authorizedFilter = await svc.genFilter("list", filter2);
|
|
1503
|
-
const trustedArgs = {
|
|
1504
|
-
...args,
|
|
1505
|
-
select: [foreignField],
|
|
1506
|
-
overrides: {
|
|
1507
|
-
filter: authorizedFilter
|
|
1508
|
-
}
|
|
1509
|
-
};
|
|
1510
|
-
const trustedOptions = {
|
|
1511
|
-
...options,
|
|
1512
|
-
lean: true,
|
|
1513
|
-
includePermissions: false,
|
|
1514
|
-
includeCount: false
|
|
1515
|
-
};
|
|
1516
|
-
const result = await svc.find(filter2, trustedArgs, trustedOptions);
|
|
1738
|
+
const localValues = docs.flatMap((doc) => this.getForeignKeyValues((0, import_utils12.get)(doc, localField)));
|
|
1739
|
+
const result = await svc.countByFieldValues(foreignField, localValues, _filters ?? {}, "count");
|
|
1517
1740
|
if (!result.success) return docs;
|
|
1518
1741
|
for (let y = 0; y < docs.length; y++) {
|
|
1519
1742
|
const doc = docs[y];
|
|
1520
|
-
|
|
1521
|
-
const filterFn = (row) => (0, import_utils12.intersectionBy)((0, import_utils12.castArray)(localValue), (0, import_utils12.castArray)((0, import_utils12.get)(row, foreignField)), String).length > 0;
|
|
1522
|
-
setDocValue(doc, path, result.data.filter(filterFn).length);
|
|
1743
|
+
setDocValue(doc, path, this.getIndexedCount(result.data, (0, import_utils12.get)(doc, localField)));
|
|
1523
1744
|
}
|
|
1524
1745
|
return docs;
|
|
1525
1746
|
}
|
|
1526
|
-
async parseClientData(filter2) {
|
|
1527
|
-
const result = await iterateQuery(
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1747
|
+
async parseClientData(filter2, scheduler = new RequestConcurrencyScheduler(this.getRequestComplexity().maxBulkConcurrency), scheduled = false) {
|
|
1748
|
+
const result = await iterateQuery(
|
|
1749
|
+
filter2,
|
|
1750
|
+
async (fo, val, key) => {
|
|
1751
|
+
switch (fo) {
|
|
1752
|
+
case 0 /* SubQuery */:
|
|
1753
|
+
return this.handleSubQuery(val, key);
|
|
1754
|
+
case 1 /* Date */:
|
|
1755
|
+
return this.handleDate(val, key);
|
|
1756
|
+
default:
|
|
1757
|
+
return null;
|
|
1758
|
+
}
|
|
1759
|
+
},
|
|
1760
|
+
scheduler,
|
|
1761
|
+
scheduled
|
|
1762
|
+
);
|
|
1537
1763
|
return result;
|
|
1538
1764
|
}
|
|
1539
1765
|
async handleSubQuery(sq, key) {
|
|
@@ -1579,7 +1805,67 @@ var import_utils14 = require("@web-ts-toolkit/utils");
|
|
|
1579
1805
|
var import_just_diff = require("just-diff");
|
|
1580
1806
|
|
|
1581
1807
|
// src/model.ts
|
|
1582
|
-
var
|
|
1808
|
+
var Model = class {
|
|
1809
|
+
constructor(modelName, runtime) {
|
|
1810
|
+
this.modelName = modelName;
|
|
1811
|
+
this.runtime = runtime ?? null;
|
|
1812
|
+
const resolvedRuntime = runtime ?? getActiveRuntime() ?? defaultRuntime;
|
|
1813
|
+
const registered = resolvedRuntime.getModelInstance(modelName);
|
|
1814
|
+
if (!registered) {
|
|
1815
|
+
throw new Error(
|
|
1816
|
+
`Runtime model registry missing model "${modelName}". Pass a mongoose.Model instance to createRouter() or register it with registerModelInstance() before using this runtime.`
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
this.mongooseModel = registered;
|
|
1820
|
+
if (!this.mongooseModel) return;
|
|
1821
|
+
}
|
|
1822
|
+
new() {
|
|
1823
|
+
const doc = new this.mongooseModel();
|
|
1824
|
+
return doc;
|
|
1825
|
+
}
|
|
1826
|
+
create(data) {
|
|
1827
|
+
return this.mongooseModel.create(data);
|
|
1828
|
+
}
|
|
1829
|
+
find({ filter: filter2, select, sort, populate, limit, hardLimit, skip, lean }) {
|
|
1830
|
+
const builder = this.mongooseModel.find(filter2);
|
|
1831
|
+
if (select) builder.select(select);
|
|
1832
|
+
if (skip) builder.skip(Number(skip));
|
|
1833
|
+
const normalizedLimit = Number(limit);
|
|
1834
|
+
if (Number.isSafeInteger(normalizedLimit) && normalizedLimit > 0) {
|
|
1835
|
+
builder.limit(normalizedLimit);
|
|
1836
|
+
} else if (Number.isSafeInteger(hardLimit) && Number(hardLimit) > 0) {
|
|
1837
|
+
builder.limit(Number(hardLimit));
|
|
1838
|
+
}
|
|
1839
|
+
if (sort) builder.sort(sort);
|
|
1840
|
+
if (populate) builder.populate(populate);
|
|
1841
|
+
if (lean) builder.lean();
|
|
1842
|
+
return builder;
|
|
1843
|
+
}
|
|
1844
|
+
findOne({ filter: filter2, select, sort, populate, lean }) {
|
|
1845
|
+
const builder = this.mongooseModel.findOne(filter2);
|
|
1846
|
+
if (select) builder.select(select);
|
|
1847
|
+
if (sort) builder.sort(sort);
|
|
1848
|
+
if (populate) builder.populate(populate);
|
|
1849
|
+
if (lean) builder.lean();
|
|
1850
|
+
return builder;
|
|
1851
|
+
}
|
|
1852
|
+
exists(filter2) {
|
|
1853
|
+
if (!filter2) return null;
|
|
1854
|
+
return this.findOne({ filter: filter2 }).select("_id").lean();
|
|
1855
|
+
}
|
|
1856
|
+
// see https://mongoosejs.com/docs/api.html#query_Query-countDocuments
|
|
1857
|
+
countDocuments(filter2 = {}) {
|
|
1858
|
+
return this.mongooseModel.countDocuments(filter2);
|
|
1859
|
+
}
|
|
1860
|
+
// see https://mongoosejs.com/docs/api.html#model_Model.distinct
|
|
1861
|
+
distinct(field, conditions = {}) {
|
|
1862
|
+
return this.mongooseModel.distinct(field, conditions);
|
|
1863
|
+
}
|
|
1864
|
+
aggregate(pipeline) {
|
|
1865
|
+
return this.mongooseModel.aggregate(pipeline);
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
var model_default = Model;
|
|
1583
1869
|
|
|
1584
1870
|
// src/logger-helpers.ts
|
|
1585
1871
|
var SENSITIVE_FILTER_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -1758,136 +2044,6 @@ var error = (error2) => {
|
|
|
1758
2044
|
safeApply(fn, target ?? defaultLogger, [error2]);
|
|
1759
2045
|
};
|
|
1760
2046
|
|
|
1761
|
-
// src/model.ts
|
|
1762
|
-
var safeSortLogError = (msg, ...args) => {
|
|
1763
|
-
const message = args.length > 0 ? `${msg} ${safeStringify(args.length === 1 ? args[0] : args)}` : msg;
|
|
1764
|
-
error(message);
|
|
1765
|
-
};
|
|
1766
|
-
var Model = class {
|
|
1767
|
-
constructor(modelName, runtime) {
|
|
1768
|
-
this.modelName = modelName;
|
|
1769
|
-
this.runtime = runtime ?? null;
|
|
1770
|
-
const resolvedRuntime = runtime ?? getActiveRuntime();
|
|
1771
|
-
const registered = resolvedRuntime?.getModelInstance(modelName) ?? null;
|
|
1772
|
-
const global = import_mongoose4.default.models[modelName];
|
|
1773
|
-
this.model = registered ?? global ?? import_mongoose4.default.model(modelName);
|
|
1774
|
-
if (!this.model) return;
|
|
1775
|
-
}
|
|
1776
|
-
new() {
|
|
1777
|
-
const doc = new this.model();
|
|
1778
|
-
return doc;
|
|
1779
|
-
}
|
|
1780
|
-
create(data) {
|
|
1781
|
-
return this.model.create(data);
|
|
1782
|
-
}
|
|
1783
|
-
find({ filter: filter2, select, sort, populate, limit, hardLimit, skip, lean }) {
|
|
1784
|
-
if (!this.validateSort(sort)) {
|
|
1785
|
-
sort = null;
|
|
1786
|
-
}
|
|
1787
|
-
const builder = this.model.find(filter2);
|
|
1788
|
-
if (select) builder.select(select);
|
|
1789
|
-
if (skip) builder.skip(Number(skip));
|
|
1790
|
-
const normalizedLimit = Number(limit);
|
|
1791
|
-
if (Number.isSafeInteger(normalizedLimit) && normalizedLimit > 0) {
|
|
1792
|
-
builder.limit(normalizedLimit);
|
|
1793
|
-
} else if (Number.isSafeInteger(hardLimit) && Number(hardLimit) > 0) {
|
|
1794
|
-
builder.limit(Number(hardLimit));
|
|
1795
|
-
}
|
|
1796
|
-
if (sort) builder.sort(sort);
|
|
1797
|
-
if (populate) builder.populate(populate);
|
|
1798
|
-
if (lean) builder.lean();
|
|
1799
|
-
return builder;
|
|
1800
|
-
}
|
|
1801
|
-
validateSort(sort, logError = safeSortLogError) {
|
|
1802
|
-
const validSortOrders = [1, -1, "asc", "ascending", "desc", "descending"];
|
|
1803
|
-
const fieldPathPattern = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/;
|
|
1804
|
-
const isValidFieldPath = (field) => fieldPathPattern.test(field);
|
|
1805
|
-
if (sort === null || sort === void 0) return true;
|
|
1806
|
-
if (typeof sort === "string") {
|
|
1807
|
-
const fields = sort.trim().split(/\s+/).filter(Boolean);
|
|
1808
|
-
const isValid = fields.every((field) => isValidFieldPath(field.startsWith("-") ? field.slice(1) : field));
|
|
1809
|
-
if (!isValid) {
|
|
1810
|
-
logError("Invalid sort string:", sort);
|
|
1811
|
-
return false;
|
|
1812
|
-
}
|
|
1813
|
-
return true;
|
|
1814
|
-
}
|
|
1815
|
-
if (Array.isArray(sort)) {
|
|
1816
|
-
const isValid = sort.every((pair) => {
|
|
1817
|
-
if (!Array.isArray(pair) || pair.length !== 2) {
|
|
1818
|
-
logError("Invalid sort array element: must be [key, order]", pair);
|
|
1819
|
-
return false;
|
|
1820
|
-
}
|
|
1821
|
-
const [key, order] = pair;
|
|
1822
|
-
if (typeof key !== "string" || !isValidFieldPath(key)) {
|
|
1823
|
-
logError("Invalid sort array key: must be a field path string", key);
|
|
1824
|
-
return false;
|
|
1825
|
-
}
|
|
1826
|
-
if (!validSortOrders.includes(order)) {
|
|
1827
|
-
logError('Invalid sort array order: must be 1, -1, "asc", or "desc"', order);
|
|
1828
|
-
return false;
|
|
1829
|
-
}
|
|
1830
|
-
return true;
|
|
1831
|
-
});
|
|
1832
|
-
return isValid;
|
|
1833
|
-
}
|
|
1834
|
-
if (typeof sort === "object" && !(sort instanceof Map)) {
|
|
1835
|
-
const isValid = Object.entries(sort).every(([key, order]) => {
|
|
1836
|
-
if (!isValidFieldPath(key)) {
|
|
1837
|
-
logError("Invalid sort object key: must be a field path string", key);
|
|
1838
|
-
return false;
|
|
1839
|
-
}
|
|
1840
|
-
if (!validSortOrders.includes(order)) {
|
|
1841
|
-
logError('Invalid sort object value: must be 1, -1, "asc", or "desc"', order);
|
|
1842
|
-
return false;
|
|
1843
|
-
}
|
|
1844
|
-
return true;
|
|
1845
|
-
});
|
|
1846
|
-
return isValid;
|
|
1847
|
-
}
|
|
1848
|
-
if (sort instanceof Map) {
|
|
1849
|
-
const isValid = Array.from(sort.entries()).every(([key, order]) => {
|
|
1850
|
-
if (!isValidFieldPath(key)) {
|
|
1851
|
-
logError("Invalid sort Map key: must be a field path string", key);
|
|
1852
|
-
return false;
|
|
1853
|
-
}
|
|
1854
|
-
if (!validSortOrders.includes(order)) {
|
|
1855
|
-
logError('Invalid sort Map value: must be 1, -1, "asc", or "desc"', order);
|
|
1856
|
-
return false;
|
|
1857
|
-
}
|
|
1858
|
-
return true;
|
|
1859
|
-
});
|
|
1860
|
-
return isValid;
|
|
1861
|
-
}
|
|
1862
|
-
logError("Invalid sort type: must be string, array, object, or Map", sort);
|
|
1863
|
-
return false;
|
|
1864
|
-
}
|
|
1865
|
-
findOne({ filter: filter2, select, sort, populate, lean }) {
|
|
1866
|
-
if (!this.validateSort(sort)) {
|
|
1867
|
-
sort = null;
|
|
1868
|
-
}
|
|
1869
|
-
const builder = this.model.findOne(filter2);
|
|
1870
|
-
if (select) builder.select(select);
|
|
1871
|
-
if (sort) builder.sort(sort);
|
|
1872
|
-
if (populate) builder.populate(populate);
|
|
1873
|
-
if (lean) builder.lean();
|
|
1874
|
-
return builder;
|
|
1875
|
-
}
|
|
1876
|
-
exists(filter2) {
|
|
1877
|
-
if (!filter2) return null;
|
|
1878
|
-
return this.findOne({ filter: filter2 }).select("_id").lean();
|
|
1879
|
-
}
|
|
1880
|
-
// see https://mongoosejs.com/docs/api.html#query_Query-countDocuments
|
|
1881
|
-
countDocuments(filter2 = {}) {
|
|
1882
|
-
return this.model.countDocuments(filter2);
|
|
1883
|
-
}
|
|
1884
|
-
// see https://mongoosejs.com/docs/api.html#model_Model.distinct
|
|
1885
|
-
distinct(field, conditions = {}) {
|
|
1886
|
-
return this.model.distinct(field, conditions);
|
|
1887
|
-
}
|
|
1888
|
-
};
|
|
1889
|
-
var model_default = Model;
|
|
1890
|
-
|
|
1891
2047
|
// src/services/model-subdocument-service.ts
|
|
1892
2048
|
var import_utils13 = require("@web-ts-toolkit/utils");
|
|
1893
2049
|
async function listSub(service, id, sub, options) {
|
|
@@ -2014,7 +2170,8 @@ async function getParentDoc(service, id, sub, args, options) {
|
|
|
2014
2170
|
const { access = "read", lean = false } = options ?? {};
|
|
2015
2171
|
const parentFilter = await service.genFilter(access, await service.genIDFilter(id));
|
|
2016
2172
|
if (parentFilter === false) return null;
|
|
2017
|
-
|
|
2173
|
+
const subPopulate = await service.genPopulate("read", genSubPopulate(sub, populate), [sub, "sub"]);
|
|
2174
|
+
return service.findRawParentDoc({ filter: parentFilter, select: sub, populate: subPopulate, lean });
|
|
2018
2175
|
}
|
|
2019
2176
|
|
|
2020
2177
|
// src/services/model-service-defaults.ts
|
|
@@ -2140,21 +2297,6 @@ var assertModelDocument = (value, modelName, hookName) => {
|
|
|
2140
2297
|
}
|
|
2141
2298
|
throw new Error(`${hookName} hook for model=${modelName} must return a Mongoose document instance`);
|
|
2142
2299
|
};
|
|
2143
|
-
var mapWithConcurrencyLimit = async (items, limit, iteratee) => {
|
|
2144
|
-
const results = new Array(items.length);
|
|
2145
|
-
let cursor = 0;
|
|
2146
|
-
const workerCount = Math.min(Math.max(limit, 1), items.length || 1);
|
|
2147
|
-
await Promise.all(
|
|
2148
|
-
Array.from({ length: workerCount }, async () => {
|
|
2149
|
-
while (cursor < items.length) {
|
|
2150
|
-
const current = cursor;
|
|
2151
|
-
cursor += 1;
|
|
2152
|
-
results[current] = await iteratee(items[current], current);
|
|
2153
|
-
}
|
|
2154
|
-
})
|
|
2155
|
-
);
|
|
2156
|
-
return results;
|
|
2157
|
-
};
|
|
2158
2300
|
var Service = class extends Base {
|
|
2159
2301
|
findRawParentDoc(args) {
|
|
2160
2302
|
return this.model.findOne({
|
|
@@ -2163,6 +2305,12 @@ var Service = class extends Base {
|
|
|
2163
2305
|
populate: args.populate
|
|
2164
2306
|
});
|
|
2165
2307
|
}
|
|
2308
|
+
createModelAdapter(modelName) {
|
|
2309
|
+
return new model_default(modelName);
|
|
2310
|
+
}
|
|
2311
|
+
getModelRouterOptions(modelName) {
|
|
2312
|
+
return getModelOptions(modelName);
|
|
2313
|
+
}
|
|
2166
2314
|
asServiceHookContext(context) {
|
|
2167
2315
|
return context;
|
|
2168
2316
|
}
|
|
@@ -2189,8 +2337,8 @@ var Service = class extends Base {
|
|
|
2189
2337
|
}
|
|
2190
2338
|
constructor(req, modelName) {
|
|
2191
2339
|
super(req, modelName);
|
|
2192
|
-
this.model =
|
|
2193
|
-
this.options =
|
|
2340
|
+
this.model = this.createModelAdapter(modelName);
|
|
2341
|
+
this.options = this.getModelRouterOptions(modelName);
|
|
2194
2342
|
this.defaults = this.options.defaults || {};
|
|
2195
2343
|
this.baseFields = ["_id"];
|
|
2196
2344
|
this.baseFieldsExt = this.baseFields.concat(this.options.documentPermissionField);
|
|
@@ -2209,10 +2357,11 @@ var Service = class extends Base {
|
|
|
2209
2357
|
if (result) return result;
|
|
2210
2358
|
throw error2;
|
|
2211
2359
|
}
|
|
2212
|
-
let [_filter, _select, _populate] = await Promise.all([
|
|
2360
|
+
let [_filter, _select, _populate, allowedSortFields] = await Promise.all([
|
|
2213
2361
|
overrideFilter || this.genFilter(access, parsedFilter),
|
|
2214
2362
|
overrideSelect || this.genQuerySelect(access, select),
|
|
2215
|
-
overridePopulate || this.genPopulate(populateAccess || access, populate)
|
|
2363
|
+
overridePopulate || this.genPopulate(populateAccess || access, populate),
|
|
2364
|
+
this.genAllowedFields({}, access, this.baseFieldsExt)
|
|
2216
2365
|
]);
|
|
2217
2366
|
const { includes, includeLocalFields, includePaths } = this.processInclude(include);
|
|
2218
2367
|
const finalSelect = normalizeSelect(_select).concat(includeLocalFields);
|
|
@@ -2231,13 +2380,18 @@ var Service = class extends Base {
|
|
|
2231
2380
|
this.completeOp("findOne", startedAt, "forbidden" /* Forbidden */, _filter);
|
|
2232
2381
|
return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
2233
2382
|
}
|
|
2383
|
+
const sortErrors = validateSortFields(sort, allowedSortFields);
|
|
2384
|
+
if (sortErrors.length > 0) {
|
|
2385
|
+
this.completeOp("findOne", startedAt, "bad_request" /* BadRequest */, _filter);
|
|
2386
|
+
return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: sortErrors, query };
|
|
2387
|
+
}
|
|
2234
2388
|
let doc = await this.model.findOne({ ...query, lean });
|
|
2235
2389
|
if (!doc) {
|
|
2236
2390
|
this.completeOp("findOne", startedAt, "not_found" /* NotFound */, _filter);
|
|
2237
2391
|
return { success: false, kind: "error", code: "not_found" /* NotFound */, query };
|
|
2238
2392
|
}
|
|
2239
2393
|
const context = {
|
|
2240
|
-
mongooseModel: this.model.
|
|
2394
|
+
mongooseModel: this.model.mongooseModel,
|
|
2241
2395
|
modelName: this.modelName,
|
|
2242
2396
|
operation: access,
|
|
2243
2397
|
originalDocumentSnapshot: toObject(doc),
|
|
@@ -2301,11 +2455,12 @@ var Service = class extends Base {
|
|
|
2301
2455
|
if (result) return result;
|
|
2302
2456
|
throw error2;
|
|
2303
2457
|
}
|
|
2304
|
-
const [_filter, _select, _populate, pagination] = await Promise.all([
|
|
2458
|
+
const [_filter, _select, _populate, pagination, allowedSortFields] = await Promise.all([
|
|
2305
2459
|
overrideFilter || this.genFilter("list", parsedFilter),
|
|
2306
2460
|
overrideSelect || this.genQuerySelect("list", select),
|
|
2307
2461
|
overridePopulate || this.genPopulate(populateAccess, populate),
|
|
2308
|
-
genPagination({ skip, limit, page, pageSize }, this.options.listHardLimit)
|
|
2462
|
+
genPagination({ skip, limit, page, pageSize }, this.options.listHardLimit),
|
|
2463
|
+
this.genAllowedFields({}, "list", this.baseFieldsExt)
|
|
2309
2464
|
]);
|
|
2310
2465
|
const finalSelect = normalizeSelect(_select);
|
|
2311
2466
|
const filteredPopulate = (0, import_utils14.isArray)(finalSelect) && (0, import_utils14.isArray)(_populate) ? _populate.filter((p) => finalSelect.includes(p.path.split(".")[0])) : _populate;
|
|
@@ -2328,13 +2483,18 @@ var Service = class extends Base {
|
|
|
2328
2483
|
this.completeOp("find", startedAt, "forbidden" /* Forbidden */, _filter);
|
|
2329
2484
|
return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
2330
2485
|
}
|
|
2486
|
+
const sortErrors = validateSortFields(sort, allowedSortFields);
|
|
2487
|
+
if (sortErrors.length > 0) {
|
|
2488
|
+
this.completeOp("find", startedAt, "bad_request" /* BadRequest */, _filter);
|
|
2489
|
+
return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: sortErrors, query };
|
|
2490
|
+
}
|
|
2331
2491
|
let docs = await this.model.find({
|
|
2332
2492
|
...query,
|
|
2333
2493
|
hardLimit: this.options.listHardLimit,
|
|
2334
2494
|
lean
|
|
2335
2495
|
});
|
|
2336
2496
|
const contexts = docs.map((doc) => ({
|
|
2337
|
-
mongooseModel: this.model.
|
|
2497
|
+
mongooseModel: this.model.mongooseModel,
|
|
2338
2498
|
modelName: this.modelName,
|
|
2339
2499
|
operation: "list",
|
|
2340
2500
|
originalDocumentSnapshot: toObject(doc),
|
|
@@ -2397,8 +2557,33 @@ var Service = class extends Base {
|
|
|
2397
2557
|
errors: [{ detail: `Bulk create exceeds maximum item count of ${maxBulkItems}` }]
|
|
2398
2558
|
};
|
|
2399
2559
|
}
|
|
2560
|
+
const parseErrors = [];
|
|
2561
|
+
const parseScheduler = new RequestConcurrencyScheduler(maxBulkConcurrency);
|
|
2400
2562
|
try {
|
|
2401
|
-
|
|
2563
|
+
const parsedData = await parseScheduler.map(dataArr, async (d, index) => {
|
|
2564
|
+
try {
|
|
2565
|
+
return await this.parseClientData(d, parseScheduler, true);
|
|
2566
|
+
} catch (error2) {
|
|
2567
|
+
const result = this.getClientRequestErrorResult(error2);
|
|
2568
|
+
if (!result) throw error2;
|
|
2569
|
+
parseErrors.push({
|
|
2570
|
+
index,
|
|
2571
|
+
code: result.code,
|
|
2572
|
+
errors: (result.errors ?? []).map((issue) => this.formatBulkValidationIssue(issue, isArr ? index : null))
|
|
2573
|
+
});
|
|
2574
|
+
return void 0;
|
|
2575
|
+
}
|
|
2576
|
+
});
|
|
2577
|
+
if (parseErrors.length > 0) {
|
|
2578
|
+
const sortedErrors = parseErrors.slice().sort((a, b) => a.index - b.index);
|
|
2579
|
+
return {
|
|
2580
|
+
success: false,
|
|
2581
|
+
kind: "error",
|
|
2582
|
+
code: sortedErrors[0]?.code ?? "bad_request" /* BadRequest */,
|
|
2583
|
+
errors: sortedErrors.flatMap((entry) => entry.errors)
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
dataArr = parsedData;
|
|
2402
2587
|
} catch (error2) {
|
|
2403
2588
|
const result = this.getClientRequestErrorResult(error2);
|
|
2404
2589
|
if (result) return result;
|
|
@@ -2409,7 +2594,7 @@ var Service = class extends Base {
|
|
|
2409
2594
|
const validationErrors = [];
|
|
2410
2595
|
const validationItems = await mapWithConcurrencyLimit(dataArr, maxBulkConcurrency, async (item, index) => {
|
|
2411
2596
|
const context = {
|
|
2412
|
-
mongooseModel: this.model.
|
|
2597
|
+
mongooseModel: this.model.mongooseModel,
|
|
2413
2598
|
modelName: this.modelName,
|
|
2414
2599
|
operation: "create",
|
|
2415
2600
|
originalData: item,
|
|
@@ -2552,7 +2737,7 @@ var Service = class extends Base {
|
|
|
2552
2737
|
return { success: false, kind: "error", code: "not_found" /* NotFound */, query };
|
|
2553
2738
|
}
|
|
2554
2739
|
const context = {
|
|
2555
|
-
mongooseModel: this.model.
|
|
2740
|
+
mongooseModel: this.model.mongooseModel,
|
|
2556
2741
|
modelName: this.modelName,
|
|
2557
2742
|
operation: "update",
|
|
2558
2743
|
resolvedQuery: query
|
|
@@ -2698,7 +2883,7 @@ var Service = class extends Base {
|
|
|
2698
2883
|
return { success: false, kind: "error", code: "not_found" /* NotFound */, query };
|
|
2699
2884
|
}
|
|
2700
2885
|
const context = {
|
|
2701
|
-
mongooseModel: this.model.
|
|
2886
|
+
mongooseModel: this.model.mongooseModel,
|
|
2702
2887
|
modelName: this.modelName,
|
|
2703
2888
|
operation: "delete",
|
|
2704
2889
|
originalDocumentSnapshot: toObject(doc),
|
|
@@ -2727,22 +2912,11 @@ var Service = class extends Base {
|
|
|
2727
2912
|
};
|
|
2728
2913
|
}
|
|
2729
2914
|
isValidDistinctFieldName(field) {
|
|
2730
|
-
|
|
2731
|
-
if (field.includes("$")) return false;
|
|
2732
|
-
if (field.includes("..")) return false;
|
|
2733
|
-
if (field.startsWith(".") || field.endsWith(".")) return false;
|
|
2734
|
-
if (/\s/.test(field)) return false;
|
|
2735
|
-
return true;
|
|
2915
|
+
return isValidFieldPath(field);
|
|
2736
2916
|
}
|
|
2737
2917
|
async authorizeDistinctField(field) {
|
|
2738
2918
|
const allowedFields = await this.genAllowedFields(null, "read");
|
|
2739
|
-
|
|
2740
|
-
if (allowed === field) return true;
|
|
2741
|
-
if (allowed.startsWith(`${field}.`)) return true;
|
|
2742
|
-
if (field.startsWith(`${allowed}.`)) return true;
|
|
2743
|
-
return false;
|
|
2744
|
-
});
|
|
2745
|
-
if (!isAllowed) {
|
|
2919
|
+
if (!isFieldAllowed(field, allowedFields)) {
|
|
2746
2920
|
return {
|
|
2747
2921
|
success: false,
|
|
2748
2922
|
kind: "error",
|
|
@@ -2780,6 +2954,44 @@ var Service = class extends Base {
|
|
|
2780
2954
|
if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
2781
2955
|
return { success: true, kind: "single", code: "success" /* Success */, data: await this.model.countDocuments(filter2), query };
|
|
2782
2956
|
}
|
|
2957
|
+
async countByFieldValues(foreignField, values, filter2 = {}, access = "list") {
|
|
2958
|
+
if (!isValidFieldPath(foreignField)) {
|
|
2959
|
+
return {
|
|
2960
|
+
success: false,
|
|
2961
|
+
kind: "error",
|
|
2962
|
+
code: "bad_request" /* BadRequest */,
|
|
2963
|
+
errors: [{ detail: `Invalid include foreignField: ${foreignField}` }]
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
const filterErrors = this.validateClientFilter(filter2);
|
|
2967
|
+
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
2968
|
+
const uniqueValues = (0, import_utils14.uniqBy)(values, (value) => String(value));
|
|
2969
|
+
filter2 = await this.genFilter(access, {
|
|
2970
|
+
...filter2,
|
|
2971
|
+
[foreignField]: { $in: uniqueValues }
|
|
2972
|
+
});
|
|
2973
|
+
const query = { filter: filter2 };
|
|
2974
|
+
if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
2975
|
+
const matchFilter = filter2;
|
|
2976
|
+
const rows = await this.model.aggregate([
|
|
2977
|
+
{ $match: matchFilter },
|
|
2978
|
+
{
|
|
2979
|
+
$project: {
|
|
2980
|
+
foreignValues: {
|
|
2981
|
+
$cond: [{ $isArray: `$${foreignField}` }, `$${foreignField}`, [`$${foreignField}`]]
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
},
|
|
2985
|
+
{ $unwind: "$foreignValues" },
|
|
2986
|
+
{ $match: { foreignValues: { $in: uniqueValues } } },
|
|
2987
|
+
{ $group: { _id: "$foreignValues", documentIds: { $addToSet: "$_id" } } }
|
|
2988
|
+
]);
|
|
2989
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2990
|
+
for (const row of rows) {
|
|
2991
|
+
counts.set(String(row._id), new Set(row.documentIds.map((id) => String(id))));
|
|
2992
|
+
}
|
|
2993
|
+
return { success: true, kind: "single", code: "success" /* Success */, data: counts, query };
|
|
2994
|
+
}
|
|
2783
2995
|
getDocPermissions(doc) {
|
|
2784
2996
|
return getDocPermissions(this.modelName, doc);
|
|
2785
2997
|
}
|
|
@@ -2891,7 +3103,7 @@ var PublicService = class extends Service {
|
|
|
2891
3103
|
return result;
|
|
2892
3104
|
}
|
|
2893
3105
|
const docs = await this.decorateAll(result.data, "list", {
|
|
2894
|
-
mongooseModel: this.model.
|
|
3106
|
+
mongooseModel: this.model.mongooseModel,
|
|
2895
3107
|
modelName: this.modelName,
|
|
2896
3108
|
operation: "list",
|
|
2897
3109
|
resolvedQuery: result.query
|
|
@@ -2940,12 +3152,17 @@ var PublicService = class extends Service {
|
|
|
2940
3152
|
{ skim, includePermissions, access, populateAccess, lean }
|
|
2941
3153
|
);
|
|
2942
3154
|
if (tryList && (!result.success || !result.data)) {
|
|
3155
|
+
const listAllowed = await this.req.macl.isAllowed(this.modelName, "list");
|
|
3156
|
+
if (!listAllowed) {
|
|
3157
|
+
return { success: false, kind: "error", code: "unauthorized" /* Unauthorized */, errors: ["Unauthorized"] };
|
|
3158
|
+
}
|
|
2943
3159
|
access = "list";
|
|
2944
3160
|
result = await this.findById(
|
|
2945
3161
|
id,
|
|
2946
3162
|
{
|
|
2947
3163
|
select,
|
|
2948
3164
|
populate,
|
|
3165
|
+
include,
|
|
2949
3166
|
overrides: { idFilter }
|
|
2950
3167
|
},
|
|
2951
3168
|
{ skim, includePermissions, access, populateAccess, lean }
|
|
@@ -2975,6 +3192,10 @@ var PublicService = class extends Service {
|
|
|
2975
3192
|
{ skim, includePermissions, access, populateAccess, lean }
|
|
2976
3193
|
);
|
|
2977
3194
|
if (tryList && (!result.success || !result.data)) {
|
|
3195
|
+
const listAllowed = await this.req.macl.isAllowed(this.modelName, "list");
|
|
3196
|
+
if (!listAllowed) {
|
|
3197
|
+
return { success: false, kind: "error", code: "unauthorized" /* Unauthorized */, errors: ["Unauthorized"] };
|
|
3198
|
+
}
|
|
2978
3199
|
access = "list";
|
|
2979
3200
|
result = await this.findOne(
|
|
2980
3201
|
filter2,
|
|
@@ -2982,6 +3203,7 @@ var PublicService = class extends Service {
|
|
|
2982
3203
|
select,
|
|
2983
3204
|
sort,
|
|
2984
3205
|
populate,
|
|
3206
|
+
include,
|
|
2985
3207
|
overrides: {}
|
|
2986
3208
|
},
|
|
2987
3209
|
{ skim, includePermissions, access, populateAccess, lean }
|
|
@@ -3171,6 +3393,7 @@ var DataService = class {
|
|
|
3171
3393
|
const filterErrors = validateClientFilter(filter2);
|
|
3172
3394
|
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
3173
3395
|
const { select, sort, skip, limit, page, pageSize } = args ?? {};
|
|
3396
|
+
const hasExplicitLimit = limit != null || pageSize != null;
|
|
3174
3397
|
const [_filter, _select, pagination] = await Promise.all([
|
|
3175
3398
|
this.genFilter("list", filter2),
|
|
3176
3399
|
this.genQuerySelect("list", select),
|
|
@@ -3183,26 +3406,26 @@ var DataService = class {
|
|
|
3183
3406
|
...pagination
|
|
3184
3407
|
};
|
|
3185
3408
|
if (_filter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: sortFieldErrors, query };
|
|
3190
|
-
}
|
|
3409
|
+
const sortFieldErrors = validateSortFields(sort, await this.genAllowedFields({}, "list"));
|
|
3410
|
+
if (sortFieldErrors.length > 0) {
|
|
3411
|
+
return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: sortFieldErrors, query };
|
|
3191
3412
|
}
|
|
3192
3413
|
let docs = await filterCollection(this.data, _filter);
|
|
3193
3414
|
const totalCount = docs.length;
|
|
3194
3415
|
if (sort) {
|
|
3195
|
-
const
|
|
3196
|
-
docs = (0, import_utils16.orderBy)(docs,
|
|
3416
|
+
const normalizedSort = normalizeSortForOrderBy(sort);
|
|
3417
|
+
docs = (0, import_utils16.orderBy)(docs, normalizedSort.fields, normalizedSort.orders);
|
|
3197
3418
|
}
|
|
3198
3419
|
const pagedDocs = docs.slice(query.skip, query.limit && query.skip + query.limit);
|
|
3199
|
-
const
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
)
|
|
3420
|
+
const { maxHookConcurrency } = this.getRequestComplexity();
|
|
3421
|
+
const trimmed = await mapWithConcurrencyLimit(pagedDocs, maxHookConcurrency, async (doc) => {
|
|
3422
|
+
doc = await this.trimOutputFields(doc, "list");
|
|
3423
|
+
if (_select.length > 0) doc = (0, import_utils16.pick)(doc, _select);
|
|
3424
|
+
return doc;
|
|
3425
|
+
});
|
|
3426
|
+
if (!hasExplicitLimit && trimmed.length === totalCount) {
|
|
3427
|
+
query.limit = trimmed.length;
|
|
3428
|
+
}
|
|
3206
3429
|
return {
|
|
3207
3430
|
success: true,
|
|
3208
3431
|
kind: "list",
|
|
@@ -3219,6 +3442,9 @@ var DataService = class {
|
|
|
3219
3442
|
decorateAll(docs, access, context) {
|
|
3220
3443
|
return this.req.dacl.decorateAll(this.dataName, docs, access, context);
|
|
3221
3444
|
}
|
|
3445
|
+
getRequestComplexity() {
|
|
3446
|
+
return resolveRequestComplexity(getGlobalOption("requestComplexity"));
|
|
3447
|
+
}
|
|
3222
3448
|
genAllowedFields(doc, access, baseFields) {
|
|
3223
3449
|
return this.req.dacl.genAllowedFields(this.dataName, doc, access, baseFields);
|
|
3224
3450
|
}
|
|
@@ -3240,26 +3466,6 @@ var DataService = class {
|
|
|
3240
3466
|
trimOutputFields(doc, access, baseFields) {
|
|
3241
3467
|
return this.pickAllowedFields(doc, access, baseFields);
|
|
3242
3468
|
}
|
|
3243
|
-
async validateSortFields(sort, access) {
|
|
3244
|
-
const fieldPathPattern = /^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)*$/;
|
|
3245
|
-
const fields = sort.trim().split(/\s+/).filter(Boolean).map((field) => ({
|
|
3246
|
-
raw: field,
|
|
3247
|
-
name: field.startsWith("-") ? field.slice(1) : field
|
|
3248
|
-
}));
|
|
3249
|
-
const errors = [];
|
|
3250
|
-
for (const { raw, name } of fields) {
|
|
3251
|
-
if (!fieldPathPattern.test(name)) {
|
|
3252
|
-
errors.push({ detail: `Invalid sort field: ${raw}`, pointer: `#/sort` });
|
|
3253
|
-
continue;
|
|
3254
|
-
}
|
|
3255
|
-
if (name === "id" || name === "_id") continue;
|
|
3256
|
-
const allowed = await this.genAllowedFields({}, access);
|
|
3257
|
-
if (!allowed.includes(name)) {
|
|
3258
|
-
errors.push({ detail: `Sort field is not allowed: ${name}`, pointer: `#/sort` });
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
return errors;
|
|
3262
|
-
}
|
|
3263
3469
|
};
|
|
3264
3470
|
|
|
3265
3471
|
// src/processors.ts
|
|
@@ -3845,7 +4051,7 @@ var Core = class {
|
|
|
3845
4051
|
alwaysSelectFields
|
|
3846
4052
|
});
|
|
3847
4053
|
}
|
|
3848
|
-
async genPopulate(modelName, access = "read", _populate = null) {
|
|
4054
|
+
async genPopulate(modelName, access = "read", _populate = null, subPaths = []) {
|
|
3849
4055
|
if (!_populate) return [];
|
|
3850
4056
|
let populate = Array.isArray(_populate) ? _populate : [_populate];
|
|
3851
4057
|
populate = (0, import_utils21.compact)(
|
|
@@ -3856,12 +4062,14 @@ var Core = class {
|
|
|
3856
4062
|
path: p.path,
|
|
3857
4063
|
select: normalizeSelect(p.select)
|
|
3858
4064
|
};
|
|
3859
|
-
const
|
|
4065
|
+
const pathForSourcePermission = subPaths.length > 0 && ret.path.startsWith(`${subPaths[0]}.`) ? ret.path.slice(subPaths[0].length + 1) : ret.path;
|
|
4066
|
+
const parentPath = pathForSourcePermission.includes(".") ? pathForSourcePermission.split(".")[0] : pathForSourcePermission;
|
|
3860
4067
|
const allowedParentPaths = await this.genSelect(
|
|
3861
4068
|
modelName,
|
|
3862
4069
|
populateAccess,
|
|
3863
4070
|
[parentPath],
|
|
3864
|
-
false
|
|
4071
|
+
false,
|
|
4072
|
+
subPaths
|
|
3865
4073
|
);
|
|
3866
4074
|
if (!allowedParentPaths.includes(parentPath)) return null;
|
|
3867
4075
|
const refModelName = getModelRef(modelName, ret.path);
|
|
@@ -4351,7 +4559,7 @@ var includeItemSchema = import_zod2.z.object({
|
|
|
4351
4559
|
filter: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional(),
|
|
4352
4560
|
localField: import_zod2.z.string().min(1),
|
|
4353
4561
|
foreignField: import_zod2.z.string().min(1),
|
|
4354
|
-
args: import_zod2.z.unknown().optional(),
|
|
4562
|
+
args: import_zod2.z.unknown().optional().describe("Forwarded to list/read includes. Count includes ignore pagination fields so counts remain exact."),
|
|
4355
4563
|
options: import_zod2.z.unknown().optional()
|
|
4356
4564
|
}).passthrough();
|
|
4357
4565
|
var includeSchema = import_zod2.z.union([includeItemSchema, import_zod2.z.array(includeItemSchema)]);
|
|
@@ -4888,7 +5096,7 @@ var import_zod4 = require("zod");
|
|
|
4888
5096
|
var dataListBodySchema = import_zod4.z.object({
|
|
4889
5097
|
filter: objectOrArraySchema.optional(),
|
|
4890
5098
|
select: projectionSchema.optional(),
|
|
4891
|
-
sort:
|
|
5099
|
+
sort: sortSchema.optional(),
|
|
4892
5100
|
skip: import_zod4.z.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
4893
5101
|
limit: import_zod4.z.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
4894
5102
|
page: import_zod4.z.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
@@ -4980,7 +5188,7 @@ var rootModelSubReadArgsSchema = import_zod5.z.object({
|
|
|
4980
5188
|
var rootModelCountOptionsSchema = import_zod5.z.object({}).strict().optional();
|
|
4981
5189
|
var rootDataListArgsSchema = import_zod5.z.object({
|
|
4982
5190
|
select: projectionSchema.optional(),
|
|
4983
|
-
sort:
|
|
5191
|
+
sort: sortSchema.optional(),
|
|
4984
5192
|
skip: import_zod5.z.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
4985
5193
|
limit: import_zod5.z.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
4986
5194
|
page: import_zod5.z.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
@@ -6220,7 +6428,12 @@ async function decorateDataListResult(svc, result, context = {}) {
|
|
|
6220
6428
|
operation: "list",
|
|
6221
6429
|
resolvedQuery: result.query
|
|
6222
6430
|
};
|
|
6223
|
-
const
|
|
6431
|
+
const { maxHookConcurrency } = svc.getRequestComplexity();
|
|
6432
|
+
const decoratedDocs = await mapWithConcurrencyLimit(
|
|
6433
|
+
result.data,
|
|
6434
|
+
maxHookConcurrency,
|
|
6435
|
+
(doc) => svc.decorate(doc, "list", decorateContext)
|
|
6436
|
+
);
|
|
6224
6437
|
const data = await svc.decorateAll(decoratedDocs, "list", decorateContext);
|
|
6225
6438
|
return {
|
|
6226
6439
|
...result,
|
|
@@ -6588,7 +6801,7 @@ var DataRouter = class {
|
|
|
6588
6801
|
const svc = this.getService(req);
|
|
6589
6802
|
const result = await svc.find(
|
|
6590
6803
|
filter2 ?? {},
|
|
6591
|
-
{ select, sort
|
|
6804
|
+
{ select, sort, skip, limit, page, pageSize },
|
|
6592
6805
|
{ includeCount }
|
|
6593
6806
|
);
|
|
6594
6807
|
handleResultError(result);
|