@web-ts-toolkit/access-router 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +255 -9
- package/advanced.d.mts +1 -1
- package/advanced.d.ts +1 -1
- package/advanced.js +40 -17
- package/advanced.mjs +42 -18
- package/index.d.mts +5 -186
- package/index.d.ts +5 -186
- package/index.js +545 -276
- package/index.mjs +408 -137
- package/llms.txt +68 -3
- package/package.json +3 -3
- package/{parsers-DppKW-Fd.d.mts → parsers-BKF7VX2-.d.mts} +339 -9
- package/{parsers-DppKW-Fd.d.ts → parsers-BKF7VX2-.d.ts} +339 -9
package/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { isPlainObject as
|
|
2
|
+
import { isPlainObject as isPlainObject21, 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 isArray12, isFunction as isFunction6, isPlainObject as
|
|
6
|
+
import { isArray as isArray12, isFunction as isFunction6, isPlainObject as isPlainObject17, isString as isString7 } from "@web-ts-toolkit/utils";
|
|
7
7
|
|
|
8
8
|
// src/core.ts
|
|
9
9
|
import {
|
|
@@ -31,7 +31,8 @@ import {
|
|
|
31
31
|
isNil as isNil2,
|
|
32
32
|
isPlainObject as isPlainObject10,
|
|
33
33
|
isString as isString4,
|
|
34
|
-
keys
|
|
34
|
+
keys,
|
|
35
|
+
normalizeUrlPath
|
|
35
36
|
} from "@web-ts-toolkit/utils";
|
|
36
37
|
|
|
37
38
|
// src/helpers/collection.ts
|
|
@@ -65,8 +66,12 @@ var mapWithConcurrencyLimit = async (items, limit, iteratee) => {
|
|
|
65
66
|
};
|
|
66
67
|
var RequestConcurrencyScheduler = class {
|
|
67
68
|
constructor(limit) {
|
|
69
|
+
this.activeWork = 0;
|
|
70
|
+
this.waiters = [];
|
|
68
71
|
this.limit = Math.max(1, limit);
|
|
69
72
|
}
|
|
73
|
+
// Each map bounds its own orchestration workers. Recursive maps never hold
|
|
74
|
+
// shared work permits while awaiting descendants (including at limit 1).
|
|
70
75
|
async map(items, iteratee) {
|
|
71
76
|
return mapWithConcurrencyLimit(items, this.limit, (item, index) => iteratee(item, index, true));
|
|
72
77
|
}
|
|
@@ -74,6 +79,23 @@ var RequestConcurrencyScheduler = class {
|
|
|
74
79
|
const [result] = await this.map([void 0], () => iteratee(true));
|
|
75
80
|
return result;
|
|
76
81
|
}
|
|
82
|
+
// Only leaf persistence operations belong here, not service/hook callbacks.
|
|
83
|
+
// All work calls on this scheduler share a FIFO admission ceiling.
|
|
84
|
+
async work(operation) {
|
|
85
|
+
if (this.activeWork >= this.limit) {
|
|
86
|
+
await new Promise((resolve) => this.waiters.push(resolve));
|
|
87
|
+
} else {
|
|
88
|
+
this.activeWork += 1;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
return await operation();
|
|
92
|
+
} finally {
|
|
93
|
+
const next = this.waiters.shift();
|
|
94
|
+
if (next)
|
|
95
|
+
next();
|
|
96
|
+
else this.activeWork -= 1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
77
99
|
};
|
|
78
100
|
|
|
79
101
|
// src/helpers/document.ts
|
|
@@ -1165,6 +1187,7 @@ var AccessRuntime = class {
|
|
|
1165
1187
|
this.modelAtts = {};
|
|
1166
1188
|
this.modelInstances = {};
|
|
1167
1189
|
this.openApiRegistry = new OpenApiRegistry();
|
|
1190
|
+
this.openApiPathPrefix = "";
|
|
1168
1191
|
this.allowGlobalModelLookup = options.allowGlobalModelLookup ?? false;
|
|
1169
1192
|
ensureMongooseJsonSchemaInitialized();
|
|
1170
1193
|
}
|
|
@@ -1193,7 +1216,24 @@ var AccessRuntime = class {
|
|
|
1193
1216
|
return global;
|
|
1194
1217
|
}
|
|
1195
1218
|
registerOpenApiRoute(route) {
|
|
1196
|
-
this.openApiRegistry.register(
|
|
1219
|
+
this.openApiRegistry.register(
|
|
1220
|
+
this.openApiPathPrefix ? { ...route, path: normalizeUrlPath(`${this.openApiPathPrefix}/${route.path}`) } : route
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Compose OpenAPI paths while synchronously constructing mounted routers.
|
|
1225
|
+
* Prefixes apply before collision checks; prior descriptors and Express paths
|
|
1226
|
+
* are unchanged. Nested calls compose and the prior scope is restored even on
|
|
1227
|
+
* failure. The callback must finish registration synchronously (no async work).
|
|
1228
|
+
*/
|
|
1229
|
+
withOpenApiPathPrefix(prefix, build) {
|
|
1230
|
+
const previous = this.openApiPathPrefix;
|
|
1231
|
+
this.openApiPathPrefix = normalizeUrlPath(`${previous}/${prefix}`);
|
|
1232
|
+
try {
|
|
1233
|
+
return build();
|
|
1234
|
+
} finally {
|
|
1235
|
+
this.openApiPathPrefix = previous;
|
|
1236
|
+
}
|
|
1197
1237
|
}
|
|
1198
1238
|
/**
|
|
1199
1239
|
* Enable strict OpenAPI collision detection for the remainder of this
|
|
@@ -1567,6 +1607,7 @@ import {
|
|
|
1567
1607
|
get as get4,
|
|
1568
1608
|
isArray as isArray7,
|
|
1569
1609
|
isPlainObject as isPlainObject12,
|
|
1610
|
+
set as set3,
|
|
1570
1611
|
uniq
|
|
1571
1612
|
} from "@web-ts-toolkit/utils";
|
|
1572
1613
|
|
|
@@ -1878,7 +1919,8 @@ function resolveValueNode(template, parent, bare) {
|
|
|
1878
1919
|
if (!isParentRefMarker(inner) || typeof inner.$parent !== "string" || (inner.$parent?.length ?? 0) === 0) {
|
|
1879
1920
|
throw new CorrelatedReferenceError('Invalid $escape payload: expected { "$escape": { "$parent": "<field>" } }');
|
|
1880
1921
|
}
|
|
1881
|
-
|
|
1922
|
+
const literal = literalParentRefObject(inner.$parent);
|
|
1923
|
+
return bare ? { $eq: literal } : literal;
|
|
1882
1924
|
}
|
|
1883
1925
|
if (isParentRefMarker(template)) {
|
|
1884
1926
|
const path = template.$parent;
|
|
@@ -2007,8 +2049,28 @@ function isCorrelatedInclude(value) {
|
|
|
2007
2049
|
return isPlainObject11(value) && value.mode === "correlated";
|
|
2008
2050
|
}
|
|
2009
2051
|
|
|
2052
|
+
// src/helpers/request-work.ts
|
|
2053
|
+
var states = /* @__PURE__ */ new WeakMap();
|
|
2054
|
+
function getRequestWorkState(req, runtime) {
|
|
2055
|
+
let owners = states.get(req);
|
|
2056
|
+
if (!owners) {
|
|
2057
|
+
owners = /* @__PURE__ */ new WeakMap();
|
|
2058
|
+
states.set(req, owners);
|
|
2059
|
+
}
|
|
2060
|
+
let state = owners.get(runtime);
|
|
2061
|
+
if (!state) {
|
|
2062
|
+
state = {
|
|
2063
|
+
totalQueries: 0,
|
|
2064
|
+
scheduler: new RequestConcurrencyScheduler(
|
|
2065
|
+
resolveRequestComplexity(runtime.getGlobalOption("requestComplexity")).maxBulkConcurrency
|
|
2066
|
+
)
|
|
2067
|
+
};
|
|
2068
|
+
owners.set(runtime, state);
|
|
2069
|
+
}
|
|
2070
|
+
return state;
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2010
2073
|
// src/services/base.ts
|
|
2011
|
-
var correlatedExecStates = /* @__PURE__ */ new WeakMap();
|
|
2012
2074
|
function getCorrelatedTemplateDepth(includes) {
|
|
2013
2075
|
let max = 0;
|
|
2014
2076
|
const visit = (entries, depth) => {
|
|
@@ -2058,6 +2120,7 @@ function validateClientFilter(filter2) {
|
|
|
2058
2120
|
}
|
|
2059
2121
|
var Base = class {
|
|
2060
2122
|
constructor(req, modelName) {
|
|
2123
|
+
this.requestRuntime = getActiveRuntime() ?? defaultRuntime;
|
|
2061
2124
|
this.req = req;
|
|
2062
2125
|
this.modelName = modelName;
|
|
2063
2126
|
}
|
|
@@ -2195,6 +2258,7 @@ var Base = class {
|
|
|
2195
2258
|
}
|
|
2196
2259
|
seenCorrelatedPaths.add(entry.path);
|
|
2197
2260
|
}
|
|
2261
|
+
this.assertIncludeOutputPaths([...legacy, ...correlated]);
|
|
2198
2262
|
let includeLocalFields = [];
|
|
2199
2263
|
let includePaths = [];
|
|
2200
2264
|
forEach3(legacy, (inc) => {
|
|
@@ -2211,6 +2275,39 @@ var Base = class {
|
|
|
2211
2275
|
correlatedReferenceFields: uniq(compact(correlatedReferenceFields))
|
|
2212
2276
|
};
|
|
2213
2277
|
}
|
|
2278
|
+
validateIncludeOutputPath(path, modelName) {
|
|
2279
|
+
const permissionField = getModelOption(modelName, "documentPermissionField");
|
|
2280
|
+
if (get4(set3({}, path, true), permissionField) !== void 0 || get4(set3({}, permissionField, true), path) !== void 0) {
|
|
2281
|
+
this.throwClientRequestError(
|
|
2282
|
+
"bad_request" /* BadRequest */,
|
|
2283
|
+
`Include output path ${path} overlaps document permission field ${permissionField} on model ${modelName}`
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
assertIncludeOutputPaths(include) {
|
|
2288
|
+
const pending = [{ entries: include, modelName: this.modelName }];
|
|
2289
|
+
const visited = /* @__PURE__ */ new WeakMap();
|
|
2290
|
+
while (pending.length > 0) {
|
|
2291
|
+
const { entries, modelName } = pending.pop();
|
|
2292
|
+
if (modelName !== this.modelName && !getModelNames().includes(modelName)) {
|
|
2293
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, `Model ${modelName} not found`);
|
|
2294
|
+
}
|
|
2295
|
+
for (const entry of entries) {
|
|
2296
|
+
if (!isPlainObject12(entry)) continue;
|
|
2297
|
+
const models = visited.get(entry) ?? /* @__PURE__ */ new Set();
|
|
2298
|
+
if (models.has(modelName)) continue;
|
|
2299
|
+
models.add(modelName);
|
|
2300
|
+
visited.set(entry, models);
|
|
2301
|
+
if (typeof entry.path === "string" && entry.path) {
|
|
2302
|
+
this.validateIncludeOutputPath(entry.path, modelName);
|
|
2303
|
+
}
|
|
2304
|
+
if ((entry.op === "read" || entry.op === "list") && typeof entry.model === "string") {
|
|
2305
|
+
const nested = entry.args?.include;
|
|
2306
|
+
if (nested) pending.push({ entries: compact(castArray(nested)), modelName: entry.model });
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2214
2311
|
getForeignKeyValues(value) {
|
|
2215
2312
|
return flatten(castArray(value));
|
|
2216
2313
|
}
|
|
@@ -2345,6 +2442,7 @@ var Base = class {
|
|
|
2345
2442
|
const svc = await this.getAuthorizedTargetService(model, "count");
|
|
2346
2443
|
const localValues = docs.flatMap((doc) => this.getForeignKeyValues(get4(doc, localField)));
|
|
2347
2444
|
const result = await svc.countByFieldValues(foreignField, localValues, _filters ?? {}, "count");
|
|
2445
|
+
if (!result.success && result.code === "bad_request" /* BadRequest */) throw new ClientRequestError(result);
|
|
2348
2446
|
if (!result.success) return docs;
|
|
2349
2447
|
for (let y = 0; y < docs.length; y++) {
|
|
2350
2448
|
const doc = docs[y];
|
|
@@ -2353,16 +2451,7 @@ var Base = class {
|
|
|
2353
2451
|
return docs;
|
|
2354
2452
|
}
|
|
2355
2453
|
getCorrelatedExecState() {
|
|
2356
|
-
|
|
2357
|
-
let state = correlatedExecStates.get(key);
|
|
2358
|
-
if (!state) {
|
|
2359
|
-
state = {
|
|
2360
|
-
totalQueries: 0,
|
|
2361
|
-
scheduler: new RequestConcurrencyScheduler(this.getRequestComplexity().maxBulkConcurrency)
|
|
2362
|
-
};
|
|
2363
|
-
correlatedExecStates.set(key, state);
|
|
2364
|
-
}
|
|
2365
|
-
return state;
|
|
2454
|
+
return getRequestWorkState(this.req, this.requestRuntime);
|
|
2366
2455
|
}
|
|
2367
2456
|
claimCorrelatedQuerySlot() {
|
|
2368
2457
|
const limits = this.getRequestComplexity();
|
|
@@ -2761,7 +2850,7 @@ var Base = class {
|
|
|
2761
2850
|
// src/services/service.ts
|
|
2762
2851
|
import {
|
|
2763
2852
|
castArray as castArray2,
|
|
2764
|
-
cloneDeep as
|
|
2853
|
+
cloneDeep as cloneDeep4,
|
|
2765
2854
|
compact as compact2,
|
|
2766
2855
|
isArray as isArray8,
|
|
2767
2856
|
isBoolean as isBoolean3,
|
|
@@ -2774,6 +2863,26 @@ import {
|
|
|
2774
2863
|
import { diff } from "just-diff";
|
|
2775
2864
|
|
|
2776
2865
|
// src/model.ts
|
|
2866
|
+
import { cloneDeep as cloneDeep3 } from "@web-ts-toolkit/utils";
|
|
2867
|
+
function admitModelPersistence(adapter, scheduler) {
|
|
2868
|
+
return {
|
|
2869
|
+
modelName: adapter.modelName,
|
|
2870
|
+
mongooseModel: adapter.mongooseModel,
|
|
2871
|
+
new: () => adapter.new(),
|
|
2872
|
+
// Mongoose create(array) otherwise starts all document saves at once. Keep
|
|
2873
|
+
// the adapter's array input/output contract, with one permit per item; all
|
|
2874
|
+
// items are submitted even if one rejects, as with unordered create(array).
|
|
2875
|
+
create: (data) => Array.isArray(data) ? Promise.all(data.map((item) => scheduler.work(async () => (await adapter.create([item]))[0]))) : scheduler.work(() => adapter.create(data)),
|
|
2876
|
+
find: (props) => scheduler.work(() => adapter.find(props)),
|
|
2877
|
+
findOne: (props) => scheduler.work(() => adapter.findOne(props)),
|
|
2878
|
+
exists: (filter2) => scheduler.work(() => adapter.exists(filter2)),
|
|
2879
|
+
countDocuments: (filter2) => scheduler.work(() => adapter.countDocuments(filter2)),
|
|
2880
|
+
distinct: (field, conditions) => scheduler.work(() => adapter.distinct(field, conditions)),
|
|
2881
|
+
aggregate: (pipeline) => scheduler.work(() => adapter.aggregate(pipeline)),
|
|
2882
|
+
// Casting builds no persistence work and must not acquire a recursive permit.
|
|
2883
|
+
...adapter.castFilter ? { castFilter: (filter2) => adapter.castFilter(filter2) } : {}
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2777
2886
|
var Model = class {
|
|
2778
2887
|
constructor(modelName, runtime) {
|
|
2779
2888
|
this.modelName = modelName;
|
|
@@ -2826,6 +2935,11 @@ var Model = class {
|
|
|
2826
2935
|
countDocuments(filter2 = {}) {
|
|
2827
2936
|
return this.mongooseModel.countDocuments(filter2);
|
|
2828
2937
|
}
|
|
2938
|
+
/** Cast a copy using this model's query schema/setters, without executing query middleware or persistence. */
|
|
2939
|
+
castFilter(filter2) {
|
|
2940
|
+
const query = this.mongooseModel.countDocuments(cloneDeep3(filter2));
|
|
2941
|
+
return query.cast(this.mongooseModel);
|
|
2942
|
+
}
|
|
2829
2943
|
// see https://mongoosejs.com/docs/api.html#model_Model.distinct
|
|
2830
2944
|
distinct(field, conditions = {}) {
|
|
2831
2945
|
return this.mongooseModel.distinct(field, conditions);
|
|
@@ -2836,6 +2950,39 @@ var Model = class {
|
|
|
2836
2950
|
};
|
|
2837
2951
|
var model_default = Model;
|
|
2838
2952
|
|
|
2953
|
+
// src/helpers/apply-update.ts
|
|
2954
|
+
import { isPlainObject as isPlainObject13, set as set4 } from "@web-ts-toolkit/utils";
|
|
2955
|
+
function applyUpdate(target, data, allowedFields) {
|
|
2956
|
+
const wholeFields = /* @__PURE__ */ new Set();
|
|
2957
|
+
const partialContainers = /* @__PURE__ */ new Set();
|
|
2958
|
+
const collectBoundary = (shape, prefix = "") => {
|
|
2959
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
2960
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
2961
|
+
if (value === true) wholeFields.add(path);
|
|
2962
|
+
else {
|
|
2963
|
+
partialContainers.add(path);
|
|
2964
|
+
collectBoundary(value, path);
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
};
|
|
2968
|
+
for (const field of allowedFields) {
|
|
2969
|
+
collectBoundary(set4({}, field, true));
|
|
2970
|
+
}
|
|
2971
|
+
const apply = (values, prefix = "") => {
|
|
2972
|
+
for (const [key, value] of Object.entries(values)) {
|
|
2973
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
2974
|
+
if (partialContainers.has(path) && !wholeFields.has(path) && (isPlainObject13(value) || Array.isArray(value))) {
|
|
2975
|
+
apply(value, path);
|
|
2976
|
+
} else if (isDocument2(target)) {
|
|
2977
|
+
target.set(path, value);
|
|
2978
|
+
} else {
|
|
2979
|
+
set4(target, path, value);
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
};
|
|
2983
|
+
apply(data ?? {});
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2839
2986
|
// src/logger-helpers.ts
|
|
2840
2987
|
var SENSITIVE_FILTER_KEYS = /* @__PURE__ */ new Set([
|
|
2841
2988
|
"password",
|
|
@@ -3015,6 +3162,30 @@ var error = (error2) => {
|
|
|
3015
3162
|
|
|
3016
3163
|
// src/services/model-subdocument-service.ts
|
|
3017
3164
|
import { forEach as forEach4, get as get5, pick as pick2 } from "@web-ts-toolkit/utils";
|
|
3165
|
+
async function visibleMutationRows(service, isAllowed, parentId, sub, rows, response) {
|
|
3166
|
+
const accesses = ["read", `subs.${sub}.read`];
|
|
3167
|
+
if (response === "create") accesses.push(`subs.${sub}.list`);
|
|
3168
|
+
const allowed = await Promise.all(accesses.map(isAllowed));
|
|
3169
|
+
if (allowed.some((value) => !value)) return [];
|
|
3170
|
+
const parentFilter = await service.genFilter("read", { _id: parentId });
|
|
3171
|
+
if (parentFilter === false) return [];
|
|
3172
|
+
const parent = await service.findRawParentDoc({
|
|
3173
|
+
filter: { $and: [{ _id: parentId }, parentFilter] },
|
|
3174
|
+
select: "_id",
|
|
3175
|
+
populate: [],
|
|
3176
|
+
lean: true
|
|
3177
|
+
});
|
|
3178
|
+
if (!parent) return [];
|
|
3179
|
+
const targetFilter = response === "create" ? {} : response === "single" ? { _id: rows[0]?._id } : { _id: { $in: rows.map((row) => row._id) } };
|
|
3180
|
+
const [readFilter, listFilter, select] = await Promise.all([
|
|
3181
|
+
service.genFilter(`subs.${sub}.read`, targetFilter),
|
|
3182
|
+
response === "create" ? service.genFilter(`subs.${sub}.list`) : Promise.resolve({}),
|
|
3183
|
+
service.genQuerySelect("read", null, false, [sub, "sub"])
|
|
3184
|
+
]);
|
|
3185
|
+
if (readFilter === false || listFilter === false) return [];
|
|
3186
|
+
const visible = filterCollection(filterCollection(rows, listFilter), readFilter);
|
|
3187
|
+
return select ? visible.map((row) => pick2(toObject(row), select.concat("_id"))) : visible;
|
|
3188
|
+
}
|
|
3018
3189
|
async function listSub(service, id, sub, options) {
|
|
3019
3190
|
const { filter: ft, select } = options ?? {};
|
|
3020
3191
|
const filterErrors = validateClientFilter(ft);
|
|
@@ -3046,25 +3217,24 @@ async function readSub(service, id, sub, subId, options) {
|
|
|
3046
3217
|
if (subSelect) subdoc = pick2(toObject(subdoc), subSelect.concat(["_id"]));
|
|
3047
3218
|
return { success: true, kind: "single", code: "success" /* Success */, data: subdoc };
|
|
3048
3219
|
}
|
|
3049
|
-
async function updateSub(service, id, sub, subId, data) {
|
|
3220
|
+
async function updateSub(service, persist, isAllowed, id, sub, subId, data) {
|
|
3050
3221
|
const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
|
|
3051
3222
|
if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
3052
3223
|
const result = get5(parentDoc, sub);
|
|
3053
|
-
const [subFilter,
|
|
3224
|
+
const [subFilter, subUpdateSelect] = await Promise.all([
|
|
3054
3225
|
service.genFilter(`subs.${sub}.update`, { _id: subId }),
|
|
3055
|
-
service.genQuerySelect("read", null, false, [sub, "sub"]),
|
|
3056
3226
|
service.genQuerySelect("update", null, false, [sub, "sub"])
|
|
3057
3227
|
]);
|
|
3058
3228
|
if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
|
|
3059
|
-
|
|
3229
|
+
const subdoc = findElement(result, subFilter);
|
|
3060
3230
|
if (!subdoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
3061
3231
|
const allowedData = pick2(data, subUpdateSelect);
|
|
3062
|
-
|
|
3063
|
-
await parentDoc.save();
|
|
3064
|
-
|
|
3065
|
-
return { success: true, kind: "single", code: "success" /* Success */, data:
|
|
3232
|
+
applyUpdate(subdoc, allowedData, subUpdateSelect);
|
|
3233
|
+
await persist(() => parentDoc.save());
|
|
3234
|
+
const visible = await visibleMutationRows(service, isAllowed, parentDoc._id, sub, [subdoc], "single");
|
|
3235
|
+
return { success: true, kind: "single", code: "success" /* Success */, data: visible[0] ?? null };
|
|
3066
3236
|
}
|
|
3067
|
-
async function bulkUpdateSub(service, id, sub, data) {
|
|
3237
|
+
async function bulkUpdateSub(service, persist, isAllowed, id, sub, data) {
|
|
3068
3238
|
const { maxBulkItems } = service.getRequestComplexity();
|
|
3069
3239
|
if (data.length > maxBulkItems) {
|
|
3070
3240
|
return {
|
|
@@ -3077,24 +3247,23 @@ async function bulkUpdateSub(service, id, sub, data) {
|
|
|
3077
3247
|
const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
|
|
3078
3248
|
if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
3079
3249
|
let result = get5(parentDoc, sub);
|
|
3080
|
-
const [subFilter,
|
|
3250
|
+
const [subFilter, subUpdateSelect] = await Promise.all([
|
|
3081
3251
|
service.genFilter(`subs.${sub}.update`, { _id: { $in: data.map((v) => v._id) } }),
|
|
3082
|
-
service.genQuerySelect("read", null, false, [sub, "sub"]),
|
|
3083
3252
|
service.genQuerySelect("update", null, false, [sub, "sub"])
|
|
3084
3253
|
]);
|
|
3085
3254
|
if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
|
|
3086
|
-
result = filterCollection(result, subFilter);
|
|
3255
|
+
result = filterCollection(result, subFilter).filter((subdoc) => findElementById(data, subdoc._id));
|
|
3087
3256
|
forEach4(result, (subdoc) => {
|
|
3088
3257
|
const tdata = findElementById(data, subdoc._id);
|
|
3089
3258
|
if (!tdata) return;
|
|
3090
3259
|
const allowedData = pick2(tdata, subUpdateSelect);
|
|
3091
|
-
|
|
3260
|
+
applyUpdate(subdoc, allowedData, subUpdateSelect);
|
|
3092
3261
|
});
|
|
3093
|
-
await parentDoc.save();
|
|
3094
|
-
|
|
3262
|
+
await persist(() => parentDoc.save());
|
|
3263
|
+
result = await visibleMutationRows(service, isAllowed, parentDoc._id, sub, result, "bulk");
|
|
3095
3264
|
return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length };
|
|
3096
3265
|
}
|
|
3097
|
-
async function createSub(service, id, sub, data, options) {
|
|
3266
|
+
async function createSub(service, persist, isAllowed, id, sub, data, options) {
|
|
3098
3267
|
const { addFirst } = options ?? {};
|
|
3099
3268
|
const { maxBulkItems } = service.getRequestComplexity();
|
|
3100
3269
|
if (Array.isArray(data) && data.length > maxBulkItems) {
|
|
@@ -3108,21 +3277,18 @@ async function createSub(service, id, sub, data, options) {
|
|
|
3108
3277
|
const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
|
|
3109
3278
|
if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
3110
3279
|
let result = get5(parentDoc, sub);
|
|
3111
|
-
const
|
|
3112
|
-
service.genQuerySelect("create", null, false, [sub, "sub"]),
|
|
3113
|
-
service.genQuerySelect("read", null, false, [sub, "sub"])
|
|
3114
|
-
]);
|
|
3280
|
+
const subCreateSelect = await service.genQuerySelect("create", null, false, [sub, "sub"]);
|
|
3115
3281
|
const allowedData = Array.isArray(data) ? data.map((row) => pick2(row, subCreateSelect)) : pick2(data, subCreateSelect);
|
|
3116
3282
|
if (Array.isArray(allowedData)) {
|
|
3117
3283
|
addFirst === true ? result.unshift(...allowedData) : result.push(...allowedData);
|
|
3118
3284
|
} else {
|
|
3119
3285
|
addFirst === true ? result.unshift(allowedData) : result.push(allowedData);
|
|
3120
3286
|
}
|
|
3121
|
-
await parentDoc.save();
|
|
3122
|
-
|
|
3287
|
+
await persist(() => parentDoc.save());
|
|
3288
|
+
result = await visibleMutationRows(service, isAllowed, parentDoc._id, sub, result, "create");
|
|
3123
3289
|
return { success: true, kind: "list", code: "created" /* Created */, data: result, count: result.length };
|
|
3124
3290
|
}
|
|
3125
|
-
async function deleteSub(service, id, sub, subId) {
|
|
3291
|
+
async function deleteSub(service, persist, id, sub, subId) {
|
|
3126
3292
|
const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
|
|
3127
3293
|
if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
3128
3294
|
const result = get5(parentDoc, sub);
|
|
@@ -3131,7 +3297,7 @@ async function deleteSub(service, id, sub, subId) {
|
|
|
3131
3297
|
const subdoc = findElement(result, subFilter);
|
|
3132
3298
|
if (!subdoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
3133
3299
|
await ("deleteOne" in subdoc ? subdoc.deleteOne?.() : subdoc.remove?.());
|
|
3134
|
-
await parentDoc.save();
|
|
3300
|
+
await persist(() => parentDoc.save());
|
|
3135
3301
|
return { success: true, kind: "single", code: "success" /* Success */, data: subdoc._id };
|
|
3136
3302
|
}
|
|
3137
3303
|
async function getParentDoc(service, id, sub, args, options) {
|
|
@@ -3290,6 +3456,15 @@ var shouldKeepCorrelatedRef = (ref, keep) => {
|
|
|
3290
3456
|
return false;
|
|
3291
3457
|
};
|
|
3292
3458
|
var Service = class extends Base {
|
|
3459
|
+
constructor(req, modelName) {
|
|
3460
|
+
super(req, modelName);
|
|
3461
|
+
this.persist = (operation) => this.getCorrelatedExecState().scheduler.work(operation);
|
|
3462
|
+
this.model = admitModelPersistence(this.createModelAdapter(modelName), this.getCorrelatedExecState().scheduler);
|
|
3463
|
+
this.options = this.getModelRouterOptions(modelName);
|
|
3464
|
+
this.defaults = this.options.defaults || {};
|
|
3465
|
+
this.baseFields = ["_id"];
|
|
3466
|
+
this.baseFieldsExt = this.baseFields.concat(this.options.documentPermissionField);
|
|
3467
|
+
}
|
|
3293
3468
|
findRawParentDoc(args) {
|
|
3294
3469
|
return this.model.findOne({
|
|
3295
3470
|
...args,
|
|
@@ -3327,14 +3502,6 @@ var Service = class extends Base {
|
|
|
3327
3502
|
...extra ?? {}
|
|
3328
3503
|
});
|
|
3329
3504
|
}
|
|
3330
|
-
constructor(req, modelName) {
|
|
3331
|
-
super(req, modelName);
|
|
3332
|
-
this.model = this.createModelAdapter(modelName);
|
|
3333
|
-
this.options = this.getModelRouterOptions(modelName);
|
|
3334
|
-
this.defaults = this.options.defaults || {};
|
|
3335
|
-
this.baseFields = ["_id"];
|
|
3336
|
-
this.baseFieldsExt = this.baseFields.concat(this.options.documentPermissionField);
|
|
3337
|
-
}
|
|
3338
3505
|
async findOne(filter2, args, options) {
|
|
3339
3506
|
const filterErrors = this.validateClientFilter(filter2);
|
|
3340
3507
|
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
@@ -3342,20 +3509,22 @@ var Service = class extends Base {
|
|
|
3342
3509
|
const { skim, includePermissions, access, populateAccess, lean } = this.resolveFindOneOptions(options);
|
|
3343
3510
|
const { filter: overrideFilter, select: overrideSelect, populate: overridePopulate } = overrides ?? {};
|
|
3344
3511
|
let parsedFilter;
|
|
3512
|
+
let processedInclude;
|
|
3345
3513
|
try {
|
|
3346
|
-
|
|
3514
|
+
processedInclude = this.processInclude(include);
|
|
3515
|
+
parsedFilter = overrideFilter === false ? false : await this.parseClientData(filter2);
|
|
3347
3516
|
} catch (error2) {
|
|
3348
3517
|
const result = this.getClientRequestErrorResult(error2);
|
|
3349
3518
|
if (result) return result;
|
|
3350
3519
|
throw error2;
|
|
3351
3520
|
}
|
|
3352
3521
|
let [_filter, _select, _populate, allowedSortFields] = await Promise.all([
|
|
3353
|
-
overrideFilter
|
|
3522
|
+
overrideFilter ?? this.genFilter(access, parsedFilter),
|
|
3354
3523
|
overrideSelect || this.genQuerySelect(access, select),
|
|
3355
3524
|
overridePopulate || this.genPopulate(populateAccess || access, populate),
|
|
3356
3525
|
this.genAllowedFields({}, access, this.baseFieldsExt)
|
|
3357
3526
|
]);
|
|
3358
|
-
const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } =
|
|
3527
|
+
const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } = processedInclude;
|
|
3359
3528
|
const correlatedOutputPaths = correlatedIncludes.map((entry) => entry.path);
|
|
3360
3529
|
const finalSelect = normalizeSelect(_select).concat(includeLocalFields, correlatedReferenceFields);
|
|
3361
3530
|
const query = {
|
|
@@ -3390,7 +3559,7 @@ var Service = class extends Base {
|
|
|
3390
3559
|
originalDocumentSnapshot: toObject(doc),
|
|
3391
3560
|
resolvedQuery: query
|
|
3392
3561
|
};
|
|
3393
|
-
const correlatedSnapshots = [
|
|
3562
|
+
const correlatedSnapshots = [cloneDeep4(toObject(doc))];
|
|
3394
3563
|
try {
|
|
3395
3564
|
doc = await this.includeDocs(doc, includes);
|
|
3396
3565
|
if (correlatedIncludes.length > 0) {
|
|
@@ -3437,7 +3606,8 @@ var Service = class extends Base {
|
|
|
3437
3606
|
const { select, populate, include, overrides } = this.resolveFindByIdArgs(args);
|
|
3438
3607
|
const { skim, includePermissions, access, populateAccess, lean } = this.resolveFindByIdOptions(options);
|
|
3439
3608
|
const { select: overrideSelect, populate: overridePopulate, idFilter: overrideIdFilter } = overrides ?? {};
|
|
3440
|
-
const filter2 = overrideIdFilter
|
|
3609
|
+
const filter2 = overrideIdFilter ?? await this.genIDFilter(id);
|
|
3610
|
+
if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query: { filter: filter2 } };
|
|
3441
3611
|
return this.findOne(
|
|
3442
3612
|
filter2,
|
|
3443
3613
|
{
|
|
@@ -3459,15 +3629,17 @@ var Service = class extends Base {
|
|
|
3459
3629
|
const { skim, includePermissions, includeCount, populateAccess, lean } = this.resolveFindOptions(options);
|
|
3460
3630
|
const { filter: overrideFilter, select: overrideSelect, populate: overridePopulate } = overrides ?? {};
|
|
3461
3631
|
let parsedFilter;
|
|
3632
|
+
let processedInclude;
|
|
3462
3633
|
try {
|
|
3463
|
-
|
|
3634
|
+
processedInclude = this.processInclude(include);
|
|
3635
|
+
parsedFilter = overrideFilter === false ? false : await this.parseClientData(filter2);
|
|
3464
3636
|
} catch (error2) {
|
|
3465
3637
|
const result = this.getClientRequestErrorResult(error2);
|
|
3466
3638
|
if (result) return result;
|
|
3467
3639
|
throw error2;
|
|
3468
3640
|
}
|
|
3469
3641
|
const [_filter, _select, _populate, pagination, allowedSortFields] = await Promise.all([
|
|
3470
|
-
overrideFilter
|
|
3642
|
+
overrideFilter ?? this.genFilter("list", parsedFilter),
|
|
3471
3643
|
overrideSelect || this.genQuerySelect("list", select),
|
|
3472
3644
|
overridePopulate || this.genPopulate(populateAccess, populate),
|
|
3473
3645
|
genPagination({ skip, limit, page, pageSize }, this.options.listHardLimit),
|
|
@@ -3475,7 +3647,7 @@ var Service = class extends Base {
|
|
|
3475
3647
|
]);
|
|
3476
3648
|
const finalSelect = normalizeSelect(_select);
|
|
3477
3649
|
const filteredPopulate = isArray8(finalSelect) && isArray8(_populate) ? _populate.filter((p) => finalSelect.includes(p.path.split(".")[0])) : _populate;
|
|
3478
|
-
const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } =
|
|
3650
|
+
const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } = processedInclude;
|
|
3479
3651
|
const correlatedOutputPaths = correlatedIncludes.map((entry) => entry.path);
|
|
3480
3652
|
const query = {
|
|
3481
3653
|
filter: _filter,
|
|
@@ -3513,7 +3685,7 @@ var Service = class extends Base {
|
|
|
3513
3685
|
resolvedQuery: query
|
|
3514
3686
|
}));
|
|
3515
3687
|
const _decorate = isFunction3(decorate) ? decorate : (v) => v;
|
|
3516
|
-
const correlatedSnapshots = docs.map((doc) =>
|
|
3688
|
+
const correlatedSnapshots = docs.map((doc) => cloneDeep4(toObject(doc)));
|
|
3517
3689
|
try {
|
|
3518
3690
|
docs = await this.includeDocs(docs, includes);
|
|
3519
3691
|
if (correlatedIncludes.length > 0) {
|
|
@@ -3685,7 +3857,7 @@ var Service = class extends Base {
|
|
|
3685
3857
|
}
|
|
3686
3858
|
if (includeDocPermissions) doc = await this.addDocPermissions(doc, "create", contexts[index]);
|
|
3687
3859
|
if (includePermissions) doc = await this.addFieldPermissions(doc, "read", contexts[index]);
|
|
3688
|
-
if (resolvedPopulate.length > 0) await populateDoc(doc, resolvedPopulate);
|
|
3860
|
+
if (resolvedPopulate.length > 0) await this.persist(() => populateDoc(doc, resolvedPopulate));
|
|
3689
3861
|
doc = await this.trimOutputFields(doc, "read", this.baseFieldsExt);
|
|
3690
3862
|
let outputDoc = await _decorate(doc, contexts[index]);
|
|
3691
3863
|
if (!includePermissions) outputDoc = this.addEmptyPermissions(outputDoc);
|
|
@@ -3741,6 +3913,13 @@ var Service = class extends Base {
|
|
|
3741
3913
|
data: doc
|
|
3742
3914
|
};
|
|
3743
3915
|
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Update at authorized policy paths: admitted leaves preserve omitted siblings;
|
|
3918
|
+
* whole-authorized objects/arrays replace. Shared by updateById and existing-row
|
|
3919
|
+
* upsert. Validation precedes trusted prepare output (not re-filtered); partial
|
|
3920
|
+
* prepare containers preserve omissions. Transform retains explicit document-setter
|
|
3921
|
+
* replacement authority. Send nested client JSON, not literal dotted update keys.
|
|
3922
|
+
*/
|
|
3744
3923
|
async updateOne(filter2, data, args, options, decorate) {
|
|
3745
3924
|
const filterErrors = this.validateClientFilter(filter2);
|
|
3746
3925
|
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
@@ -3748,7 +3927,7 @@ var Service = class extends Base {
|
|
|
3748
3927
|
const { skim, includePermissions, populateAccess } = this.resolveUpdateOneOptions(options);
|
|
3749
3928
|
const { filter: overrideFilter, populate: overridePopulate } = overrides ?? {};
|
|
3750
3929
|
const [_filter, _populate] = await Promise.all([
|
|
3751
|
-
overrideFilter
|
|
3930
|
+
overrideFilter ?? this.genFilter("update", filter2),
|
|
3752
3931
|
overridePopulate || this.genPopulate(populateAccess, populate)
|
|
3753
3932
|
]);
|
|
3754
3933
|
const query = { filter: _filter, populate: _populate };
|
|
@@ -3803,11 +3982,11 @@ var Service = class extends Base {
|
|
|
3803
3982
|
}
|
|
3804
3983
|
const prepared = await this.prepare(allowedData, "update", context);
|
|
3805
3984
|
context.preparedData = prepared;
|
|
3806
|
-
|
|
3985
|
+
applyUpdate(doc, prepared, allowedFields);
|
|
3807
3986
|
context.modifiedPaths = doc.modifiedPaths();
|
|
3808
3987
|
doc = assertModelDocument(await this.transform(doc, "update", context), this.modelName, "transform");
|
|
3809
3988
|
context.currentDocument = doc;
|
|
3810
|
-
doc = await doc.save();
|
|
3989
|
+
doc = await this.persist(() => doc.save());
|
|
3811
3990
|
const diffExcludeFields = [this.options.documentPermissionField, "__v"];
|
|
3812
3991
|
this.asServiceHookContext(context).diff = (d) => {
|
|
3813
3992
|
context.changes = diff(
|
|
@@ -3827,7 +4006,7 @@ var Service = class extends Base {
|
|
|
3827
4006
|
}
|
|
3828
4007
|
if (includeDocPermissions) doc = await this.addDocPermissions(doc, "update", context);
|
|
3829
4008
|
if (includePermissions) doc = await this.addFieldPermissions(doc, "update", context);
|
|
3830
|
-
if (_populate) await populateDoc(doc, _populate);
|
|
4009
|
+
if (_populate) await this.persist(() => populateDoc(doc, _populate));
|
|
3831
4010
|
doc = await this.trimOutputFields(doc, "read", this.baseFieldsExt);
|
|
3832
4011
|
let outputDoc = doc;
|
|
3833
4012
|
if (isFunction3(decorate)) outputDoc = await decorate(outputDoc, context);
|
|
@@ -3839,7 +4018,8 @@ var Service = class extends Base {
|
|
|
3839
4018
|
const { populate, overrides } = this.resolveUpdateByIdArgs(args);
|
|
3840
4019
|
const { skim, includePermissions, populateAccess } = this.resolveUpdateByIdOptions(options);
|
|
3841
4020
|
const { populate: overridePopulate, idFilter: overrideIdFilter } = overrides;
|
|
3842
|
-
const filter2 = overrideIdFilter
|
|
4021
|
+
const filter2 = overrideIdFilter ?? await this.genIDFilter(id);
|
|
4022
|
+
if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query: { filter: filter2 } };
|
|
3843
4023
|
return this.updateOne(
|
|
3844
4024
|
filter2,
|
|
3845
4025
|
data,
|
|
@@ -3859,7 +4039,7 @@ var Service = class extends Base {
|
|
|
3859
4039
|
const { populate, overrides } = this.resolveUpsertArgs(args);
|
|
3860
4040
|
const { skim, includePermissions, populateAccess } = this.resolveUpsertOptions(options);
|
|
3861
4041
|
const { filter: overrideFilter, populate: overridePopulate } = overrides ?? {};
|
|
3862
|
-
const _filter = await (overrideFilter
|
|
4042
|
+
const _filter = await (overrideFilter ?? this.genFilter("update", filter2));
|
|
3863
4043
|
const query = { filter: _filter };
|
|
3864
4044
|
const startedAt = this.beginOp("upsert", _filter);
|
|
3865
4045
|
if (_filter === false) {
|
|
@@ -3919,7 +4099,9 @@ var Service = class extends Base {
|
|
|
3919
4099
|
resolvedQuery: query
|
|
3920
4100
|
};
|
|
3921
4101
|
await this.beforeDelete(doc, context);
|
|
3922
|
-
await
|
|
4102
|
+
await this.persist(
|
|
4103
|
+
() => "deleteOne" in doc ? doc.deleteOne() : doc.remove()
|
|
4104
|
+
);
|
|
3923
4105
|
context.finalDocumentSnapshot = toObject(doc);
|
|
3924
4106
|
await this.afterDelete(doc, context);
|
|
3925
4107
|
this.completeOp("delete", startedAt, "success" /* Success */, filter2);
|
|
@@ -3930,6 +4112,7 @@ var Service = class extends Base {
|
|
|
3930
4112
|
if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
3931
4113
|
const { access, includeId } = this.resolveExistsOptions(options);
|
|
3932
4114
|
filter2 = await this.genFilter(access, filter2);
|
|
4115
|
+
if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query: { filter: filter2 } };
|
|
3933
4116
|
const result = await this.model.exists(filter2);
|
|
3934
4117
|
return {
|
|
3935
4118
|
success: true,
|
|
@@ -3999,6 +4182,13 @@ var Service = class extends Base {
|
|
|
3999
4182
|
query
|
|
4000
4183
|
};
|
|
4001
4184
|
}
|
|
4185
|
+
/**
|
|
4186
|
+
* Group target document IDs by original parent-key aliases using one aggregate.
|
|
4187
|
+
* Resolves the requested row policy (legacy count includes pass `count`), casts
|
|
4188
|
+
* authorized filters/foreign operands with the target schema, and deduplicates IDs
|
|
4189
|
+
* independently of pagination. Cast failures return BadRequest; false policy returns
|
|
4190
|
+
* Forbidden. Casting does not execute countDocuments middleware/plugins.
|
|
4191
|
+
*/
|
|
4002
4192
|
async countByFieldValues(foreignField, values, filter2 = {}, access = "list") {
|
|
4003
4193
|
if (!isValidFieldPath(foreignField)) {
|
|
4004
4194
|
return {
|
|
@@ -4017,7 +4207,25 @@ var Service = class extends Base {
|
|
|
4017
4207
|
});
|
|
4018
4208
|
const query = { filter: filter2 };
|
|
4019
4209
|
if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
4020
|
-
|
|
4210
|
+
let matchFilter = filter2;
|
|
4211
|
+
let castValues = uniqueValues;
|
|
4212
|
+
try {
|
|
4213
|
+
if (this.model.castFilter) {
|
|
4214
|
+
matchFilter = this.model.castFilter(matchFilter);
|
|
4215
|
+
const foreignFilter = this.model.castFilter({ [foreignField]: { $in: uniqueValues } });
|
|
4216
|
+
castValues = foreignFilter[foreignField]?.$in;
|
|
4217
|
+
if (!Array.isArray(castValues) || castValues.length !== uniqueValues.length) {
|
|
4218
|
+
throw new Error("Foreign field query casting must retain the join operands");
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
} catch {
|
|
4222
|
+
return {
|
|
4223
|
+
success: false,
|
|
4224
|
+
kind: "error",
|
|
4225
|
+
code: "bad_request" /* BadRequest */,
|
|
4226
|
+
errors: [{ detail: "Invalid grouped include count filter or foreign-key value" }]
|
|
4227
|
+
};
|
|
4228
|
+
}
|
|
4021
4229
|
const rows = await this.model.aggregate([
|
|
4022
4230
|
{ $match: matchFilter },
|
|
4023
4231
|
{
|
|
@@ -4028,12 +4236,20 @@ var Service = class extends Base {
|
|
|
4028
4236
|
}
|
|
4029
4237
|
},
|
|
4030
4238
|
{ $unwind: "$foreignValues" },
|
|
4031
|
-
{ $match: { foreignValues: { $in:
|
|
4239
|
+
{ $match: { foreignValues: { $in: castValues } } },
|
|
4032
4240
|
{ $group: { _id: "$foreignValues", documentIds: { $addToSet: "$_id" } } }
|
|
4033
4241
|
]);
|
|
4034
|
-
const
|
|
4242
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4035
4243
|
for (const row of rows) {
|
|
4036
|
-
|
|
4244
|
+
const key = String(row._id);
|
|
4245
|
+
const ids = grouped.get(key) ?? /* @__PURE__ */ new Set();
|
|
4246
|
+
for (const id of row.documentIds) ids.add(String(id));
|
|
4247
|
+
grouped.set(key, ids);
|
|
4248
|
+
}
|
|
4249
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4250
|
+
for (let i = 0; i < uniqueValues.length; i++) {
|
|
4251
|
+
const ids = grouped.get(String(castValues[i]));
|
|
4252
|
+
if (ids) counts.set(String(uniqueValues[i]), ids);
|
|
4037
4253
|
}
|
|
4038
4254
|
return { success: true, kind: "single", code: "success" /* Success */, data: counts, query };
|
|
4039
4255
|
}
|
|
@@ -4112,17 +4328,60 @@ var Service = class extends Base {
|
|
|
4112
4328
|
async readSub(id, sub, subId, options) {
|
|
4113
4329
|
return readSub(this, id, sub, subId, options);
|
|
4114
4330
|
}
|
|
4331
|
+
/**
|
|
4332
|
+
* Apply authorized policy-path updates, preserving protected siblings, then save.
|
|
4333
|
+
* Output requires post-save parent read and subdocument read operation/row access;
|
|
4334
|
+
* hidden output is successful `data: null` (direct/root-entry 200), not a failed write.
|
|
4335
|
+
* Visible output uses read fields plus `_id`. Do not retry solely for null output.
|
|
4336
|
+
*/
|
|
4115
4337
|
async updateSub(id, sub, subId, data) {
|
|
4116
|
-
return updateSub(
|
|
4338
|
+
return updateSub(
|
|
4339
|
+
this,
|
|
4340
|
+
this.persist,
|
|
4341
|
+
(access) => this.req.macl.isAllowed(this.modelName, access),
|
|
4342
|
+
id,
|
|
4343
|
+
sub,
|
|
4344
|
+
subId,
|
|
4345
|
+
data
|
|
4346
|
+
);
|
|
4117
4347
|
}
|
|
4348
|
+
/**
|
|
4349
|
+
* Save authorized targeted updates, then return readable targets in stored order.
|
|
4350
|
+
* Requires parent/subdocument read access for output, not subdocument list access;
|
|
4351
|
+
* uses read fields plus `_id`. Hidden output succeeds with `data: [], count: 0`
|
|
4352
|
+
* (direct/root-entry 200); count measures visible rows, not all updated rows.
|
|
4353
|
+
*/
|
|
4118
4354
|
async bulkUpdateSub(id, sub, data) {
|
|
4119
|
-
return bulkUpdateSub(
|
|
4355
|
+
return bulkUpdateSub(
|
|
4356
|
+
this,
|
|
4357
|
+
this.persist,
|
|
4358
|
+
(access) => this.req.macl.isAllowed(this.modelName, access),
|
|
4359
|
+
id,
|
|
4360
|
+
sub,
|
|
4361
|
+
castArray2(data)
|
|
4362
|
+
);
|
|
4120
4363
|
}
|
|
4364
|
+
/**
|
|
4365
|
+
* Append/prepend admitted rows, then return the visible full array (including when
|
|
4366
|
+
* input is empty). Output requires post-save parent read and subdocument list AND
|
|
4367
|
+
* read guards/row filters; fields use read projection plus `_id`.
|
|
4368
|
+
* Hidden output succeeds with code `created`, `data: [], count: 0`
|
|
4369
|
+
* (direct/root-entry 201). Visible count/order follow filtered stored rows.
|
|
4370
|
+
* Empty output does not mean the write failed; addFirst is a service option.
|
|
4371
|
+
*/
|
|
4121
4372
|
async createSub(id, sub, data, options) {
|
|
4122
|
-
return createSub(
|
|
4373
|
+
return createSub(
|
|
4374
|
+
this,
|
|
4375
|
+
this.persist,
|
|
4376
|
+
(access) => this.req.macl.isAllowed(this.modelName, access),
|
|
4377
|
+
id,
|
|
4378
|
+
sub,
|
|
4379
|
+
data,
|
|
4380
|
+
options
|
|
4381
|
+
);
|
|
4123
4382
|
}
|
|
4124
4383
|
async deleteSub(id, sub, subId) {
|
|
4125
|
-
return deleteSub(this, id, sub, subId);
|
|
4384
|
+
return deleteSub(this, this.persist, id, sub, subId);
|
|
4126
4385
|
}
|
|
4127
4386
|
async getParentDoc(id, sub, args, options) {
|
|
4128
4387
|
return getParentDoc(this, id, sub, args, options);
|
|
@@ -4196,6 +4455,7 @@ var PublicService = class extends Service {
|
|
|
4196
4455
|
},
|
|
4197
4456
|
{ skim, includePermissions, access, populateAccess, lean }
|
|
4198
4457
|
);
|
|
4458
|
+
if (!result.success && (result.code === "bad_request" /* BadRequest */ || result.code === "forbidden" /* Forbidden */)) return result;
|
|
4199
4459
|
if (tryList && (!result.success || !result.data)) {
|
|
4200
4460
|
const listAllowed = await this.req.macl.isAllowed(this.modelName, "list");
|
|
4201
4461
|
if (!listAllowed) {
|
|
@@ -4236,6 +4496,7 @@ var PublicService = class extends Service {
|
|
|
4236
4496
|
},
|
|
4237
4497
|
{ skim, includePermissions, access, populateAccess, lean }
|
|
4238
4498
|
);
|
|
4499
|
+
if (!result.success && (result.code === "bad_request" /* BadRequest */ || result.code === "forbidden" /* Forbidden */)) return result;
|
|
4239
4500
|
if (tryList && (!result.success || !result.data)) {
|
|
4240
4501
|
const listAllowed = await this.req.macl.isAllowed(this.modelName, "list");
|
|
4241
4502
|
if (!listAllowed) {
|
|
@@ -4284,6 +4545,11 @@ var PublicService = class extends Service {
|
|
|
4284
4545
|
}
|
|
4285
4546
|
return result;
|
|
4286
4547
|
}
|
|
4548
|
+
/**
|
|
4549
|
+
* With an _id, check update-policy existence then update: terminal denial returns
|
|
4550
|
+
* Forbidden, an allowed miss remains Unauthorized, and neither creates a row.
|
|
4551
|
+
* Without an _id, use the create path.
|
|
4552
|
+
*/
|
|
4287
4553
|
async _upsert(data, args, options) {
|
|
4288
4554
|
const idKey = this.getIdentifier();
|
|
4289
4555
|
if (idKey !== "_id") {
|
|
@@ -4514,7 +4780,7 @@ var DataService = class {
|
|
|
4514
4780
|
};
|
|
4515
4781
|
|
|
4516
4782
|
// src/processors.ts
|
|
4517
|
-
import { cloneDeep as
|
|
4783
|
+
import { cloneDeep as cloneDeep5, forEach as forEach6, get as get7, hasOwn, isArray as isArray9, isPlainObject as isPlainObject15, map, set as set6 } from "@web-ts-toolkit/utils";
|
|
4518
4784
|
var UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
4519
4785
|
var DEFAULT_MAX_SLICE = 1e3;
|
|
4520
4786
|
var DEFAULT_MASK_REPLACEMENT = "***";
|
|
@@ -4549,7 +4815,7 @@ function resolveLeafParents(obj, src) {
|
|
|
4549
4815
|
for (let ind = 0; ind < segs.length - 1; ind += 1) {
|
|
4550
4816
|
const seg = segs[ind];
|
|
4551
4817
|
targets = targets.reduce((ret, target) => {
|
|
4552
|
-
if (!
|
|
4818
|
+
if (!isPlainObject15(target)) return ret;
|
|
4553
4819
|
const targetObject = target;
|
|
4554
4820
|
const next = targetObject[seg];
|
|
4555
4821
|
if (isArray9(next)) {
|
|
@@ -4563,7 +4829,7 @@ function resolveLeafParents(obj, src) {
|
|
|
4563
4829
|
const leaf = segs[segs.length - 1];
|
|
4564
4830
|
const pairs = [];
|
|
4565
4831
|
forEach6(targets, (target) => {
|
|
4566
|
-
if (!
|
|
4832
|
+
if (!isPlainObject15(target)) return;
|
|
4567
4833
|
pairs.push({ parent: target, leaf });
|
|
4568
4834
|
});
|
|
4569
4835
|
return pairs;
|
|
@@ -4571,7 +4837,7 @@ function resolveLeafParents(obj, src) {
|
|
|
4571
4837
|
var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
|
|
4572
4838
|
const mutable = options.mutable !== false;
|
|
4573
4839
|
const idField = options.idField ?? "_id";
|
|
4574
|
-
const obj = mutable ? docObject :
|
|
4840
|
+
const obj = mutable ? docObject : cloneDeep5(docObject);
|
|
4575
4841
|
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4576
4842
|
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
4577
4843
|
const src = op.src;
|
|
@@ -4584,7 +4850,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
4584
4850
|
forEach6(segs, (seg, ind) => {
|
|
4585
4851
|
if (segs.length === ind + 1) {
|
|
4586
4852
|
forEach6(targets, (target) => {
|
|
4587
|
-
if (!
|
|
4853
|
+
if (!isPlainObject15(target)) return;
|
|
4588
4854
|
const targetObject = target;
|
|
4589
4855
|
const leaf = targetObject[seg];
|
|
4590
4856
|
const sourceForDest = leaf;
|
|
@@ -4592,7 +4858,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
4592
4858
|
if (isArray9(leaf)) {
|
|
4593
4859
|
if (leaf.length === 0) {
|
|
4594
4860
|
depopulated = [];
|
|
4595
|
-
} else if (leaf.every((item) =>
|
|
4861
|
+
} else if (leaf.every((item) => isPlainObject15(item))) {
|
|
4596
4862
|
depopulated = map(leaf, idField).map((id, i) => {
|
|
4597
4863
|
if (id === void 0) {
|
|
4598
4864
|
failMissingId(`${src}[${i}]`);
|
|
@@ -4602,7 +4868,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
4602
4868
|
} else {
|
|
4603
4869
|
return;
|
|
4604
4870
|
}
|
|
4605
|
-
} else if (
|
|
4871
|
+
} else if (isPlainObject15(leaf)) {
|
|
4606
4872
|
const id = get7(leaf, idField);
|
|
4607
4873
|
if (id === void 0) {
|
|
4608
4874
|
failMissingId(src);
|
|
@@ -4611,12 +4877,12 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
4611
4877
|
} else {
|
|
4612
4878
|
return;
|
|
4613
4879
|
}
|
|
4614
|
-
|
|
4615
|
-
|
|
4880
|
+
set6(targetObject, dest, sourceForDest);
|
|
4881
|
+
set6(targetObject, seg, depopulated);
|
|
4616
4882
|
});
|
|
4617
4883
|
} else {
|
|
4618
4884
|
targets = targets.reduce((ret, target) => {
|
|
4619
|
-
if (!
|
|
4885
|
+
if (!isPlainObject15(target)) return ret;
|
|
4620
4886
|
const targetObject = target;
|
|
4621
4887
|
const next = targetObject[seg];
|
|
4622
4888
|
if (isArray9(next)) {
|
|
@@ -4633,7 +4899,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
4633
4899
|
};
|
|
4634
4900
|
var copyPaths = (docObject, operations, options = { mutable: true }) => {
|
|
4635
4901
|
const mutable = options.mutable !== false;
|
|
4636
|
-
const obj = mutable ? docObject :
|
|
4902
|
+
const obj = mutable ? docObject : cloneDeep5(docObject);
|
|
4637
4903
|
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4638
4904
|
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
4639
4905
|
const src = op.src;
|
|
@@ -4644,14 +4910,14 @@ var copyPaths = (docObject, operations, options = { mutable: true }) => {
|
|
|
4644
4910
|
if (src === dest) return;
|
|
4645
4911
|
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4646
4912
|
if (!hasOwn(parent, leaf)) return;
|
|
4647
|
-
|
|
4913
|
+
set6(parent, dest, cloneDeep5(parent[leaf]));
|
|
4648
4914
|
});
|
|
4649
4915
|
});
|
|
4650
4916
|
return obj;
|
|
4651
4917
|
};
|
|
4652
4918
|
var movePaths = (docObject, operations, options = { mutable: true }) => {
|
|
4653
4919
|
const mutable = options.mutable !== false;
|
|
4654
|
-
const obj = mutable ? docObject :
|
|
4920
|
+
const obj = mutable ? docObject : cloneDeep5(docObject);
|
|
4655
4921
|
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4656
4922
|
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
4657
4923
|
const src = op.src;
|
|
@@ -4662,8 +4928,8 @@ var movePaths = (docObject, operations, options = { mutable: true }) => {
|
|
|
4662
4928
|
if (src === dest) return;
|
|
4663
4929
|
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4664
4930
|
if (!hasOwn(parent, leaf)) return;
|
|
4665
|
-
const value =
|
|
4666
|
-
|
|
4931
|
+
const value = cloneDeep5(parent[leaf]);
|
|
4932
|
+
set6(parent, dest, value);
|
|
4667
4933
|
if (dest === leaf || dest.startsWith(`${leaf}.`)) return;
|
|
4668
4934
|
delete parent[leaf];
|
|
4669
4935
|
});
|
|
@@ -4674,7 +4940,7 @@ var sliceArrays = (docObject, operations, options = { mutable: true, maxSlice: D
|
|
|
4674
4940
|
const mutable = options.mutable !== false;
|
|
4675
4941
|
const maxSlice = options.maxSlice ?? DEFAULT_MAX_SLICE;
|
|
4676
4942
|
assertNonNegativeSafeInteger(maxSlice, "maxSlice", "sliceArrays");
|
|
4677
|
-
const obj = mutable ? docObject :
|
|
4943
|
+
const obj = mutable ? docObject : cloneDeep5(docObject);
|
|
4678
4944
|
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4679
4945
|
if (!op || typeof op.src !== "string") return;
|
|
4680
4946
|
const src = op.src;
|
|
@@ -4698,9 +4964,9 @@ var sliceArrays = (docObject, operations, options = { mutable: true, maxSlice: D
|
|
|
4698
4964
|
const end = typeof limit === "undefined" ? void 0 : skip + limit;
|
|
4699
4965
|
const window = leafValue.slice(skip, end);
|
|
4700
4966
|
if (typeof rawDest === "undefined") {
|
|
4701
|
-
|
|
4967
|
+
set6(parent, leaf, window);
|
|
4702
4968
|
} else {
|
|
4703
|
-
|
|
4969
|
+
set6(parent, rawDest, window);
|
|
4704
4970
|
}
|
|
4705
4971
|
});
|
|
4706
4972
|
});
|
|
@@ -4708,7 +4974,7 @@ var sliceArrays = (docObject, operations, options = { mutable: true, maxSlice: D
|
|
|
4708
4974
|
};
|
|
4709
4975
|
var countPaths = (docObject, operations, options = { mutable: true }) => {
|
|
4710
4976
|
const mutable = options.mutable !== false;
|
|
4711
|
-
const obj = mutable ? docObject :
|
|
4977
|
+
const obj = mutable ? docObject : cloneDeep5(docObject);
|
|
4712
4978
|
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4713
4979
|
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
4714
4980
|
const src = op.src;
|
|
@@ -4722,21 +4988,21 @@ var countPaths = (docObject, operations, options = { mutable: true }) => {
|
|
|
4722
4988
|
let count;
|
|
4723
4989
|
if (isArray9(leafValue)) {
|
|
4724
4990
|
count = leafValue.length;
|
|
4725
|
-
} else if (
|
|
4991
|
+
} else if (isPlainObject15(leafValue)) {
|
|
4726
4992
|
count = Object.keys(leafValue).length;
|
|
4727
4993
|
} else if (leafValue === null || typeof leafValue === "undefined") {
|
|
4728
4994
|
count = 0;
|
|
4729
4995
|
} else {
|
|
4730
4996
|
count = 1;
|
|
4731
4997
|
}
|
|
4732
|
-
|
|
4998
|
+
set6(parent, dest, count);
|
|
4733
4999
|
});
|
|
4734
5000
|
});
|
|
4735
5001
|
return obj;
|
|
4736
5002
|
};
|
|
4737
5003
|
var maskPaths = (docObject, operations, options = { mutable: true }) => {
|
|
4738
5004
|
const mutable = options.mutable !== false;
|
|
4739
|
-
const obj = mutable ? docObject :
|
|
5005
|
+
const obj = mutable ? docObject : cloneDeep5(docObject);
|
|
4740
5006
|
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4741
5007
|
if (!op || typeof op.src !== "string") return;
|
|
4742
5008
|
const src = op.src;
|
|
@@ -4745,7 +5011,7 @@ var maskPaths = (docObject, operations, options = { mutable: true }) => {
|
|
|
4745
5011
|
const replacement = Object.prototype.hasOwnProperty.call(op, "replacement") ? op.replacement : DEFAULT_MASK_REPLACEMENT;
|
|
4746
5012
|
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4747
5013
|
if (!hasOwn(parent, leaf)) return;
|
|
4748
|
-
|
|
5014
|
+
set6(parent, leaf, replacement);
|
|
4749
5015
|
});
|
|
4750
5016
|
});
|
|
4751
5017
|
return obj;
|
|
@@ -4798,7 +5064,7 @@ import {
|
|
|
4798
5064
|
isEmpty as isEmpty2,
|
|
4799
5065
|
isEqual,
|
|
4800
5066
|
isFunction as isFunction4,
|
|
4801
|
-
isPlainObject as
|
|
5067
|
+
isPlainObject as isPlainObject16,
|
|
4802
5068
|
isString as isString5
|
|
4803
5069
|
} from "@web-ts-toolkit/utils";
|
|
4804
5070
|
|
|
@@ -4839,10 +5105,10 @@ var permission_default = Permission;
|
|
|
4839
5105
|
|
|
4840
5106
|
// src/core-shared.ts
|
|
4841
5107
|
function isAndFilter(filter2) {
|
|
4842
|
-
return !!filter2 &&
|
|
5108
|
+
return !!filter2 && isPlainObject16(filter2) && Object.keys(filter2).length === 1 && isArray10(filter2.$and);
|
|
4843
5109
|
}
|
|
4844
5110
|
function isMergeableClause(filter2) {
|
|
4845
|
-
return !!filter2 &&
|
|
5111
|
+
return !!filter2 && isPlainObject16(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
|
|
4846
5112
|
}
|
|
4847
5113
|
function optimizeAndFilter(clausesInput) {
|
|
4848
5114
|
const clauses = [];
|
|
@@ -4860,7 +5126,7 @@ function optimizeAndFilter(clausesInput) {
|
|
|
4860
5126
|
if (isArray10(value)) {
|
|
4861
5127
|
return `[${value.map(dedupeKey).join(",")}]`;
|
|
4862
5128
|
}
|
|
4863
|
-
if (
|
|
5129
|
+
if (isPlainObject16(value)) {
|
|
4864
5130
|
return `{${Object.keys(value).sort().map((key) => `${key}:${dedupeKey(value[key])}`).join(",")}}`;
|
|
4865
5131
|
}
|
|
4866
5132
|
return JSON.stringify(value);
|
|
@@ -4902,7 +5168,7 @@ function optimizeAndFilter(clausesInput) {
|
|
|
4902
5168
|
}
|
|
4903
5169
|
function normalizeFilter(filter2) {
|
|
4904
5170
|
if (filter2 === false) return false;
|
|
4905
|
-
if (!filter2 || !
|
|
5171
|
+
if (!filter2 || !isPlainObject16(filter2) || isEmpty2(filter2)) return null;
|
|
4906
5172
|
if (!isAndFilter(filter2)) {
|
|
4907
5173
|
return filter2;
|
|
4908
5174
|
}
|
|
@@ -4969,7 +5235,7 @@ async function setRequestPermissions(req) {
|
|
|
4969
5235
|
const globalPermissions = getGlobalOption("globalPermissions");
|
|
4970
5236
|
if (!isFunction4(globalPermissions)) return;
|
|
4971
5237
|
const permissions = await globalPermissions.call(req, req);
|
|
4972
|
-
if (
|
|
5238
|
+
if (isPlainObject16(permissions)) req[requestPermissionField] = permissions;
|
|
4973
5239
|
else if (isArray10(permissions)) req[requestPermissionField] = arrayToRecord(permissions);
|
|
4974
5240
|
else if (isString5(permissions)) req[requestPermissionField] = { [permissions]: true };
|
|
4975
5241
|
owners.set(requestPermissionField, activeRuntime);
|
|
@@ -5582,12 +5848,12 @@ function guard(condition) {
|
|
|
5582
5848
|
const permissions = req[PERMISSIONS];
|
|
5583
5849
|
let cond = condition;
|
|
5584
5850
|
let phas = (key) => permissions.has(key);
|
|
5585
|
-
if (
|
|
5851
|
+
if (isPlainObject17(condition)) {
|
|
5586
5852
|
const { modelName, id, condition: _cond } = condition;
|
|
5587
5853
|
const svc = req.macl.getPublicService(modelName);
|
|
5588
5854
|
const select = getModelOption(modelName, `alwaysSelectFields.read`, void 0);
|
|
5589
5855
|
let _id = isString7(id) ? id : null;
|
|
5590
|
-
if (
|
|
5856
|
+
if (isPlainObject17(id)) {
|
|
5591
5857
|
const { type = "param", key } = id;
|
|
5592
5858
|
if (type === "param") {
|
|
5593
5859
|
const paramValue = req.params[key];
|
|
@@ -5629,7 +5895,7 @@ import express2 from "express";
|
|
|
5629
5895
|
|
|
5630
5896
|
// src/routers/model-router.ts
|
|
5631
5897
|
import JsonRouter5 from "@web-ts-toolkit/express-json-router";
|
|
5632
|
-
import { forEach as forEach8, isPlainObject as
|
|
5898
|
+
import { forEach as forEach8, isPlainObject as isPlainObject19, isString as isString8, isUndefined as isUndefined2, normalizeUrlPath as normalizeUrlPath3, padEnd } from "@web-ts-toolkit/utils";
|
|
5633
5899
|
|
|
5634
5900
|
// src/routers/router-mutation.ts
|
|
5635
5901
|
var MODEL_BUILD_TIME_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -5758,7 +6024,7 @@ import JsonRouter4 from "@web-ts-toolkit/express-json-router";
|
|
|
5758
6024
|
|
|
5759
6025
|
// src/validation/common.ts
|
|
5760
6026
|
import { z as z2 } from "zod";
|
|
5761
|
-
import { isPlainObject as
|
|
6027
|
+
import { isPlainObject as isPlainObject18 } from "@web-ts-toolkit/utils";
|
|
5762
6028
|
var stringOrStringArray = z2.union([z2.string(), z2.array(z2.string())]);
|
|
5763
6029
|
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);
|
|
5764
6030
|
var queryBooleanString = z2.enum(["true", "false"]);
|
|
@@ -5858,7 +6124,7 @@ var correlatedFilterSchema = z2.record(z2.string(), z2.unknown()).superRefine((f
|
|
|
5858
6124
|
}
|
|
5859
6125
|
});
|
|
5860
6126
|
var correlatedEmptyOptionsSchema = z2.unknown().refine(
|
|
5861
|
-
(value) => value === void 0 ||
|
|
6127
|
+
(value) => value === void 0 || isPlainObject18(value) && Object.keys(value).length === 0,
|
|
5862
6128
|
"Correlated include options must be absent or empty"
|
|
5863
6129
|
).optional();
|
|
5864
6130
|
var correlatedPathSchema = z2.string().min(1).superRefine((path, ctx) => {
|
|
@@ -5895,7 +6161,7 @@ var correlatedListArgsSchema = z2.object({
|
|
|
5895
6161
|
include: z2.lazy(() => correlatedNestedIncludeSchema).optional()
|
|
5896
6162
|
}).strict().superRefine((args, ctx) => checkArgsMarkers(args.select, args.sort, ctx));
|
|
5897
6163
|
var correlatedCountArgsSchema = z2.unknown().refine(
|
|
5898
|
-
(value) => value === void 0 ||
|
|
6164
|
+
(value) => value === void 0 || isPlainObject18(value) && Object.keys(value).length === 0,
|
|
5899
6165
|
"Count includes accept no args"
|
|
5900
6166
|
).optional();
|
|
5901
6167
|
var correlatedReadByIdSchema = z2.object({
|
|
@@ -5945,7 +6211,7 @@ var correlatedNestedIncludeSchema = z2.union([includeItemSchema, z2.array(includ
|
|
|
5945
6211
|
var rejectDuplicateCorrelatedPaths = (items, ctx) => {
|
|
5946
6212
|
const seen = /* @__PURE__ */ new Set();
|
|
5947
6213
|
items.forEach((item, index) => {
|
|
5948
|
-
if (
|
|
6214
|
+
if (isPlainObject18(item) && item.mode === "correlated" && typeof item.path === "string") {
|
|
5949
6215
|
if (seen.has(item.path)) {
|
|
5950
6216
|
ctx.addIssue({
|
|
5951
6217
|
code: z2.ZodIssueCode.custom,
|
|
@@ -6144,19 +6410,12 @@ function fromJoi(schema) {
|
|
|
6144
6410
|
}
|
|
6145
6411
|
function fromAjv(validator) {
|
|
6146
6412
|
return async (value) => {
|
|
6147
|
-
|
|
6148
|
-
if (isThenable(outcome)) {
|
|
6413
|
+
if (validator.$async === true) {
|
|
6149
6414
|
try {
|
|
6150
|
-
const data = await
|
|
6151
|
-
if (data === false) {
|
|
6152
|
-
return {
|
|
6153
|
-
success: false,
|
|
6154
|
-
issues: normalizeAjvIssues(validator.errors ?? [])
|
|
6155
|
-
};
|
|
6156
|
-
}
|
|
6415
|
+
const data = await validator(value);
|
|
6157
6416
|
return {
|
|
6158
6417
|
success: true,
|
|
6159
|
-
data
|
|
6418
|
+
data
|
|
6160
6419
|
};
|
|
6161
6420
|
} catch (error2) {
|
|
6162
6421
|
if (!isAjvValidationError(error2)) {
|
|
@@ -6164,11 +6423,21 @@ function fromAjv(validator) {
|
|
|
6164
6423
|
}
|
|
6165
6424
|
return {
|
|
6166
6425
|
success: false,
|
|
6167
|
-
issues: normalizeAjvIssues(error2.errors
|
|
6426
|
+
issues: normalizeAjvIssues(error2.errors)
|
|
6168
6427
|
};
|
|
6169
6428
|
}
|
|
6170
6429
|
}
|
|
6171
|
-
const
|
|
6430
|
+
const outcome = validator(value);
|
|
6431
|
+
if (isThenable(outcome)) {
|
|
6432
|
+
void Promise.resolve(outcome).catch(() => {
|
|
6433
|
+
});
|
|
6434
|
+
throw new TypeError(
|
|
6435
|
+
"fromAjv: promise/thenable validators must declare $async: true and resolve with validated data"
|
|
6436
|
+
);
|
|
6437
|
+
}
|
|
6438
|
+
if (typeof outcome !== "boolean") {
|
|
6439
|
+
throw new TypeError("fromAjv: synchronous validators must return a boolean verdict");
|
|
6440
|
+
}
|
|
6172
6441
|
if (outcome) {
|
|
6173
6442
|
return {
|
|
6174
6443
|
success: true,
|
|
@@ -6177,7 +6446,9 @@ function fromAjv(validator) {
|
|
|
6177
6446
|
}
|
|
6178
6447
|
return {
|
|
6179
6448
|
success: false,
|
|
6180
|
-
|
|
6449
|
+
// Normalize immediately, copying message/path before another call can
|
|
6450
|
+
// replace the errors array or mutate its entries.
|
|
6451
|
+
issues: normalizeAjvIssues(validator.errors ?? [])
|
|
6181
6452
|
};
|
|
6182
6453
|
};
|
|
6183
6454
|
}
|
|
@@ -6407,14 +6678,14 @@ function isArkTypeErrors(value) {
|
|
|
6407
6678
|
return record.arkKind === "errors" || record[" arkKind"] === "errors";
|
|
6408
6679
|
}
|
|
6409
6680
|
function isThenable(value) {
|
|
6410
|
-
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
6681
|
+
return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
|
|
6411
6682
|
}
|
|
6412
6683
|
function isAjvValidationError(error2) {
|
|
6413
6684
|
if (typeof error2 !== "object" || error2 === null || !("errors" in error2)) {
|
|
6414
6685
|
return false;
|
|
6415
6686
|
}
|
|
6416
|
-
const
|
|
6417
|
-
return
|
|
6687
|
+
const candidate = error2;
|
|
6688
|
+
return candidate.ajv === true && candidate.validation === true && Array.isArray(candidate.errors);
|
|
6418
6689
|
}
|
|
6419
6690
|
function isVineValidationError(error2) {
|
|
6420
6691
|
if (typeof error2 !== "object" || error2 === null || !("messages" in error2)) {
|
|
@@ -7507,11 +7778,11 @@ function setModelSubDocumentRoutes(context) {
|
|
|
7507
7778
|
}
|
|
7508
7779
|
|
|
7509
7780
|
// src/openapi/route-registration.ts
|
|
7510
|
-
import { normalizeUrlPath } from "@web-ts-toolkit/utils";
|
|
7781
|
+
import { normalizeUrlPath as normalizeUrlPath2 } from "@web-ts-toolkit/utils";
|
|
7511
7782
|
function registerOpenApiRoute(runtime, basePath, defaultTag, route) {
|
|
7512
7783
|
runtime.registerOpenApiRoute({
|
|
7513
7784
|
...route,
|
|
7514
|
-
path:
|
|
7785
|
+
path: normalizeUrlPath2(basePath + route.path),
|
|
7515
7786
|
tags: route.tags ?? [defaultTag]
|
|
7516
7787
|
});
|
|
7517
7788
|
}
|
|
@@ -7682,7 +7953,7 @@ var ModelRouter = class _ModelRouter {
|
|
|
7682
7953
|
this.runtime = runtime;
|
|
7683
7954
|
this.runtime.setModelOptions(modelName, initialOptions);
|
|
7684
7955
|
this.options = this.runtime.getModelOptions(modelName);
|
|
7685
|
-
this.fullBasePath =
|
|
7956
|
+
this.fullBasePath = normalizeUrlPath3(this.options.parentPath + this.options.basePath);
|
|
7686
7957
|
this.modelName = modelName;
|
|
7687
7958
|
this.router = new JsonRouter5(this.options.basePath, createSetCore(this.runtime), accessRouterResponseHandler);
|
|
7688
7959
|
this.model = new model_default(modelName, this.runtime);
|
|
@@ -7763,7 +8034,7 @@ var ModelRouter = class _ModelRouter {
|
|
|
7763
8034
|
logEndpoints() {
|
|
7764
8035
|
runWithRuntime(this.runtime, () => {
|
|
7765
8036
|
forEach8(this.router.getEndpoints(), ({ method, path }) => {
|
|
7766
|
-
logInfoMessage(`${padEnd(method, 6)} ${
|
|
8037
|
+
logInfoMessage(`${padEnd(method, 6)} ${normalizeUrlPath3(this.options.parentPath + path)}`);
|
|
7767
8038
|
});
|
|
7768
8039
|
});
|
|
7769
8040
|
}
|
|
@@ -7776,7 +8047,7 @@ var ModelRouter = class _ModelRouter {
|
|
|
7776
8047
|
value
|
|
7777
8048
|
);
|
|
7778
8049
|
}
|
|
7779
|
-
if (arguments.length === 1 &&
|
|
8050
|
+
if (arguments.length === 1 && isPlainObject19(keyOrOptions)) {
|
|
7780
8051
|
assertMutableRouterOptions("model", keyOrOptions);
|
|
7781
8052
|
this.runtime.setModelOptions(this.modelName, keyOrOptions);
|
|
7782
8053
|
}
|
|
@@ -7799,7 +8070,7 @@ var ModelRouter = class _ModelRouter {
|
|
|
7799
8070
|
|
|
7800
8071
|
// src/routers/root-router.ts
|
|
7801
8072
|
import JsonRouter6 from "@web-ts-toolkit/express-json-router";
|
|
7802
|
-
import { isNumber as _isNumber, normalizeUrlPath as
|
|
8073
|
+
import { isNumber as _isNumber, normalizeUrlPath as normalizeUrlPath4, orderBy as _orderBy } from "@web-ts-toolkit/utils";
|
|
7803
8074
|
|
|
7804
8075
|
// src/core-data.ts
|
|
7805
8076
|
var DataCore = class {
|
|
@@ -8065,7 +8336,7 @@ var RootRouter = class {
|
|
|
8065
8336
|
this.setRoutes();
|
|
8066
8337
|
}
|
|
8067
8338
|
registerOpenApiRoute(route) {
|
|
8068
|
-
registerOpenApiRoute(this.runtime,
|
|
8339
|
+
registerOpenApiRoute(this.runtime, normalizeUrlPath4(this.basename), "root", route);
|
|
8069
8340
|
}
|
|
8070
8341
|
wrapResult(index, item, result) {
|
|
8071
8342
|
return {
|
|
@@ -8215,7 +8486,7 @@ var RootRouter = class {
|
|
|
8215
8486
|
|
|
8216
8487
|
// src/routers/data-router.ts
|
|
8217
8488
|
import JsonRouter7 from "@web-ts-toolkit/express-json-router";
|
|
8218
|
-
import { isPlainObject as
|
|
8489
|
+
import { isPlainObject as isPlainObject20, isString as isString9, isUndefined as isUndefined3, normalizeUrlPath as normalizeUrlPath5 } from "@web-ts-toolkit/utils";
|
|
8219
8490
|
var clientErrors4 = JsonRouter7.clientErrors;
|
|
8220
8491
|
function setOption2(parentKey, optionKey, option) {
|
|
8221
8492
|
const key = isUndefined3(option) ? parentKey : `${parentKey}.${optionKey}`;
|
|
@@ -8260,7 +8531,7 @@ var DataRouter = class {
|
|
|
8260
8531
|
this.runtime = runtime;
|
|
8261
8532
|
this.runtime.setDataOptions(dataName, initialOptions);
|
|
8262
8533
|
this.options = this.runtime.getDataOptions(dataName);
|
|
8263
|
-
this.fullBasePath =
|
|
8534
|
+
this.fullBasePath = normalizeUrlPath5(this.options.parentPath + this.options.basePath);
|
|
8264
8535
|
this.dataName = dataName;
|
|
8265
8536
|
this.router = new JsonRouter7(this.options.basePath, createSetDataCore(this.runtime), accessRouterResponseHandler);
|
|
8266
8537
|
this.setCollectionRoutes();
|
|
@@ -8416,7 +8687,7 @@ var DataRouter = class {
|
|
|
8416
8687
|
assertMutableRouterOption("data", keyOrOptions);
|
|
8417
8688
|
this.runtime.setDataOption(this.dataName, keyOrOptions, value);
|
|
8418
8689
|
}
|
|
8419
|
-
if (arguments.length === 1 &&
|
|
8690
|
+
if (arguments.length === 1 && isPlainObject20(keyOrOptions)) {
|
|
8420
8691
|
assertMutableRouterOptions("data", keyOrOptions);
|
|
8421
8692
|
this.runtime.setDataOptions(this.dataName, keyOrOptions);
|
|
8422
8693
|
}
|
|
@@ -8563,7 +8834,7 @@ function createRuntimeApi(runtime) {
|
|
|
8563
8834
|
if (arguments.length === 2 && isString10(keyOrOptions)) {
|
|
8564
8835
|
return runtime.setGlobalOption(keyOrOptions, value);
|
|
8565
8836
|
}
|
|
8566
|
-
if (arguments.length === 1 &&
|
|
8837
|
+
if (arguments.length === 1 && isPlainObject21(keyOrOptions)) {
|
|
8567
8838
|
return runtime.setGlobalOptions(keyOrOptions);
|
|
8568
8839
|
}
|
|
8569
8840
|
};
|