@web-ts-toolkit/access-router 0.45.0 → 0.47.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/index.js CHANGED
@@ -77,14 +77,14 @@ __export(index_exports, {
77
77
  setModelOptions: () => setModelOptions
78
78
  });
79
79
  module.exports = __toCommonJS(index_exports);
80
- var import_utils30 = require("@web-ts-toolkit/utils");
80
+ var import_utils32 = require("@web-ts-toolkit/utils");
81
81
 
82
82
  // src/middleware.ts
83
83
  var import_express_json_router2 = __toESM(require("@web-ts-toolkit/express-json-router"));
84
- var import_utils23 = require("@web-ts-toolkit/utils");
84
+ var import_utils25 = require("@web-ts-toolkit/utils");
85
85
 
86
86
  // src/core.ts
87
- var import_utils22 = require("@web-ts-toolkit/utils");
87
+ var import_utils24 = require("@web-ts-toolkit/utils");
88
88
 
89
89
  // src/runtime.ts
90
90
  var import_mongoose3 = __toESM(require("mongoose"));
@@ -122,8 +122,12 @@ var mapWithConcurrencyLimit = async (items, limit, iteratee) => {
122
122
  };
123
123
  var RequestConcurrencyScheduler = class {
124
124
  constructor(limit) {
125
+ this.activeWork = 0;
126
+ this.waiters = [];
125
127
  this.limit = Math.max(1, limit);
126
128
  }
129
+ // Each map bounds its own orchestration workers. Recursive maps never hold
130
+ // shared work permits while awaiting descendants (including at limit 1).
127
131
  async map(items, iteratee) {
128
132
  return mapWithConcurrencyLimit(items, this.limit, (item, index) => iteratee(item, index, true));
129
133
  }
@@ -131,6 +135,23 @@ var RequestConcurrencyScheduler = class {
131
135
  const [result] = await this.map([void 0], () => iteratee(true));
132
136
  return result;
133
137
  }
138
+ // Only leaf persistence operations belong here, not service/hook callbacks.
139
+ // All work calls on this scheduler share a FIFO admission ceiling.
140
+ async work(operation) {
141
+ if (this.activeWork >= this.limit) {
142
+ await new Promise((resolve) => this.waiters.push(resolve));
143
+ } else {
144
+ this.activeWork += 1;
145
+ }
146
+ try {
147
+ return await operation();
148
+ } finally {
149
+ const next = this.waiters.shift();
150
+ if (next)
151
+ next();
152
+ else this.activeWork -= 1;
153
+ }
154
+ }
134
155
  };
135
156
 
136
157
  // src/helpers/document.ts
@@ -355,6 +376,41 @@ function normalizeSortForOrderBy(sort) {
355
376
  orders: normalized.fields.map(({ direction }) => direction)
356
377
  };
357
378
  }
379
+ function sanitizeSortFields(sort, allowedFields) {
380
+ const { fields, errors } = normalizeSort(sort);
381
+ if (errors.length > 0) return { sort: void 0, stripped: [], errors };
382
+ const allowedSet = new Set(allowedFields.concat(["id", "_id"]));
383
+ const kept = fields.filter(({ field }) => allowedSet.has(field));
384
+ const stripped = fields.filter(({ field }) => !allowedSet.has(field)).map(({ field }) => field);
385
+ if (stripped.length === 0) return { sort, stripped, errors: [] };
386
+ if (kept.length === 0) return { sort: void 0, stripped, errors: [] };
387
+ if (typeof sort === "string") {
388
+ return {
389
+ sort: kept.map(({ field, direction }) => direction === "desc" ? `-${field}` : field).join(" "),
390
+ stripped,
391
+ errors: []
392
+ };
393
+ }
394
+ if (sort instanceof Map) {
395
+ return {
396
+ sort: new Map(kept.map(({ field, direction }) => [field, direction])),
397
+ stripped,
398
+ errors: []
399
+ };
400
+ }
401
+ if (Array.isArray(sort)) {
402
+ return {
403
+ sort: kept.map(({ field, direction }) => [field, direction]),
404
+ stripped,
405
+ errors: []
406
+ };
407
+ }
408
+ return {
409
+ sort: Object.fromEntries(kept.map(({ field, direction }) => [field, direction])),
410
+ stripped,
411
+ errors: []
412
+ };
413
+ }
358
414
 
359
415
  // src/helpers/index.ts
360
416
  var import_utils6 = require("@web-ts-toolkit/utils");
@@ -1222,6 +1278,7 @@ var AccessRuntime = class {
1222
1278
  this.modelAtts = {};
1223
1279
  this.modelInstances = {};
1224
1280
  this.openApiRegistry = new OpenApiRegistry();
1281
+ this.openApiPathPrefix = "";
1225
1282
  this.allowGlobalModelLookup = options.allowGlobalModelLookup ?? false;
1226
1283
  ensureMongooseJsonSchemaInitialized();
1227
1284
  }
@@ -1250,7 +1307,24 @@ var AccessRuntime = class {
1250
1307
  return global;
1251
1308
  }
1252
1309
  registerOpenApiRoute(route) {
1253
- this.openApiRegistry.register(route);
1310
+ this.openApiRegistry.register(
1311
+ this.openApiPathPrefix ? { ...route, path: (0, import_utils11.normalizeUrlPath)(`${this.openApiPathPrefix}/${route.path}`) } : route
1312
+ );
1313
+ }
1314
+ /**
1315
+ * Compose OpenAPI paths while synchronously constructing mounted routers.
1316
+ * Prefixes apply before collision checks; prior descriptors and Express paths
1317
+ * are unchanged. Nested calls compose and the prior scope is restored even on
1318
+ * failure. The callback must finish registration synchronously (no async work).
1319
+ */
1320
+ withOpenApiPathPrefix(prefix, build) {
1321
+ const previous = this.openApiPathPrefix;
1322
+ this.openApiPathPrefix = (0, import_utils11.normalizeUrlPath)(`${previous}/${prefix}`);
1323
+ try {
1324
+ return build();
1325
+ } finally {
1326
+ this.openApiPathPrefix = previous;
1327
+ }
1254
1328
  }
1255
1329
  /**
1256
1330
  * Enable strict OpenAPI collision detection for the remainder of this
@@ -1925,7 +1999,8 @@ function resolveValueNode(template, parent, bare) {
1925
1999
  if (!isParentRefMarker(inner) || typeof inner.$parent !== "string" || (inner.$parent?.length ?? 0) === 0) {
1926
2000
  throw new CorrelatedReferenceError('Invalid $escape payload: expected { "$escape": { "$parent": "<field>" } }');
1927
2001
  }
1928
- return literalParentRefObject(inner.$parent);
2002
+ const literal = literalParentRefObject(inner.$parent);
2003
+ return bare ? { $eq: literal } : literal;
1929
2004
  }
1930
2005
  if (isParentRefMarker(template)) {
1931
2006
  const path = template.$parent;
@@ -2054,8 +2129,28 @@ function isCorrelatedInclude(value) {
2054
2129
  return (0, import_utils12.isPlainObject)(value) && value.mode === "correlated";
2055
2130
  }
2056
2131
 
2132
+ // src/helpers/request-work.ts
2133
+ var states = /* @__PURE__ */ new WeakMap();
2134
+ function getRequestWorkState(req, runtime) {
2135
+ let owners = states.get(req);
2136
+ if (!owners) {
2137
+ owners = /* @__PURE__ */ new WeakMap();
2138
+ states.set(req, owners);
2139
+ }
2140
+ let state = owners.get(runtime);
2141
+ if (!state) {
2142
+ state = {
2143
+ totalQueries: 0,
2144
+ scheduler: new RequestConcurrencyScheduler(
2145
+ resolveRequestComplexity(runtime.getGlobalOption("requestComplexity")).maxBulkConcurrency
2146
+ )
2147
+ };
2148
+ owners.set(runtime, state);
2149
+ }
2150
+ return state;
2151
+ }
2152
+
2057
2153
  // src/services/base.ts
2058
- var correlatedExecStates = /* @__PURE__ */ new WeakMap();
2059
2154
  function getCorrelatedTemplateDepth(includes) {
2060
2155
  let max = 0;
2061
2156
  const visit = (entries, depth) => {
@@ -2105,6 +2200,7 @@ function validateClientFilter(filter2) {
2105
2200
  }
2106
2201
  var Base = class {
2107
2202
  constructor(req, modelName) {
2203
+ this.requestRuntime = getActiveRuntime() ?? defaultRuntime;
2108
2204
  this.req = req;
2109
2205
  this.modelName = modelName;
2110
2206
  }
@@ -2242,6 +2338,7 @@ var Base = class {
2242
2338
  }
2243
2339
  seenCorrelatedPaths.add(entry.path);
2244
2340
  }
2341
+ this.assertIncludeOutputPaths([...legacy, ...correlated]);
2245
2342
  let includeLocalFields = [];
2246
2343
  let includePaths = [];
2247
2344
  (0, import_utils13.forEach)(legacy, (inc) => {
@@ -2258,6 +2355,39 @@ var Base = class {
2258
2355
  correlatedReferenceFields: (0, import_utils13.uniq)((0, import_utils13.compact)(correlatedReferenceFields))
2259
2356
  };
2260
2357
  }
2358
+ validateIncludeOutputPath(path, modelName) {
2359
+ const permissionField = getModelOption(modelName, "documentPermissionField");
2360
+ if ((0, import_utils13.get)((0, import_utils13.set)({}, path, true), permissionField) !== void 0 || (0, import_utils13.get)((0, import_utils13.set)({}, permissionField, true), path) !== void 0) {
2361
+ this.throwClientRequestError(
2362
+ "bad_request" /* BadRequest */,
2363
+ `Include output path ${path} overlaps document permission field ${permissionField} on model ${modelName}`
2364
+ );
2365
+ }
2366
+ }
2367
+ assertIncludeOutputPaths(include) {
2368
+ const pending = [{ entries: include, modelName: this.modelName }];
2369
+ const visited = /* @__PURE__ */ new WeakMap();
2370
+ while (pending.length > 0) {
2371
+ const { entries, modelName } = pending.pop();
2372
+ if (modelName !== this.modelName && !getModelNames().includes(modelName)) {
2373
+ this.throwClientRequestError("bad_request" /* BadRequest */, `Model ${modelName} not found`);
2374
+ }
2375
+ for (const entry of entries) {
2376
+ if (!(0, import_utils13.isPlainObject)(entry)) continue;
2377
+ const models = visited.get(entry) ?? /* @__PURE__ */ new Set();
2378
+ if (models.has(modelName)) continue;
2379
+ models.add(modelName);
2380
+ visited.set(entry, models);
2381
+ if (typeof entry.path === "string" && entry.path) {
2382
+ this.validateIncludeOutputPath(entry.path, modelName);
2383
+ }
2384
+ if ((entry.op === "read" || entry.op === "list") && typeof entry.model === "string") {
2385
+ const nested = entry.args?.include;
2386
+ if (nested) pending.push({ entries: (0, import_utils13.compact)((0, import_utils13.castArray)(nested)), modelName: entry.model });
2387
+ }
2388
+ }
2389
+ }
2390
+ }
2261
2391
  getForeignKeyValues(value) {
2262
2392
  return (0, import_utils13.flatten)((0, import_utils13.castArray)(value));
2263
2393
  }
@@ -2392,6 +2522,7 @@ var Base = class {
2392
2522
  const svc = await this.getAuthorizedTargetService(model, "count");
2393
2523
  const localValues = docs.flatMap((doc) => this.getForeignKeyValues((0, import_utils13.get)(doc, localField)));
2394
2524
  const result = await svc.countByFieldValues(foreignField, localValues, _filters ?? {}, "count");
2525
+ if (!result.success && result.code === "bad_request" /* BadRequest */) throw new ClientRequestError(result);
2395
2526
  if (!result.success) return docs;
2396
2527
  for (let y = 0; y < docs.length; y++) {
2397
2528
  const doc = docs[y];
@@ -2400,16 +2531,7 @@ var Base = class {
2400
2531
  return docs;
2401
2532
  }
2402
2533
  getCorrelatedExecState() {
2403
- const key = this.req;
2404
- let state = correlatedExecStates.get(key);
2405
- if (!state) {
2406
- state = {
2407
- totalQueries: 0,
2408
- scheduler: new RequestConcurrencyScheduler(this.getRequestComplexity().maxBulkConcurrency)
2409
- };
2410
- correlatedExecStates.set(key, state);
2411
- }
2412
- return state;
2534
+ return getRequestWorkState(this.req, this.requestRuntime);
2413
2535
  }
2414
2536
  claimCorrelatedQuerySlot() {
2415
2537
  const limits = this.getRequestComplexity();
@@ -2806,10 +2928,30 @@ var Base = class {
2806
2928
  };
2807
2929
 
2808
2930
  // src/services/service.ts
2809
- var import_utils15 = require("@web-ts-toolkit/utils");
2931
+ var import_utils17 = require("@web-ts-toolkit/utils");
2810
2932
  var import_just_diff = require("just-diff");
2811
2933
 
2812
2934
  // src/model.ts
2935
+ var import_utils14 = require("@web-ts-toolkit/utils");
2936
+ function admitModelPersistence(adapter, scheduler) {
2937
+ return {
2938
+ modelName: adapter.modelName,
2939
+ mongooseModel: adapter.mongooseModel,
2940
+ new: () => adapter.new(),
2941
+ // Mongoose create(array) otherwise starts all document saves at once. Keep
2942
+ // the adapter's array input/output contract, with one permit per item; all
2943
+ // items are submitted even if one rejects, as with unordered create(array).
2944
+ create: (data) => Array.isArray(data) ? Promise.all(data.map((item) => scheduler.work(async () => (await adapter.create([item]))[0]))) : scheduler.work(() => adapter.create(data)),
2945
+ find: (props) => scheduler.work(() => adapter.find(props)),
2946
+ findOne: (props) => scheduler.work(() => adapter.findOne(props)),
2947
+ exists: (filter2) => scheduler.work(() => adapter.exists(filter2)),
2948
+ countDocuments: (filter2) => scheduler.work(() => adapter.countDocuments(filter2)),
2949
+ distinct: (field, conditions) => scheduler.work(() => adapter.distinct(field, conditions)),
2950
+ aggregate: (pipeline) => scheduler.work(() => adapter.aggregate(pipeline)),
2951
+ // Casting builds no persistence work and must not acquire a recursive permit.
2952
+ ...adapter.castFilter ? { castFilter: (filter2) => adapter.castFilter(filter2) } : {}
2953
+ };
2954
+ }
2813
2955
  var Model = class {
2814
2956
  constructor(modelName, runtime) {
2815
2957
  this.modelName = modelName;
@@ -2862,6 +3004,11 @@ var Model = class {
2862
3004
  countDocuments(filter2 = {}) {
2863
3005
  return this.mongooseModel.countDocuments(filter2);
2864
3006
  }
3007
+ /** Cast a copy using this model's query schema/setters, without executing query middleware or persistence. */
3008
+ castFilter(filter2) {
3009
+ const query = this.mongooseModel.countDocuments((0, import_utils14.cloneDeep)(filter2));
3010
+ return query.cast(this.mongooseModel);
3011
+ }
2865
3012
  // see https://mongoosejs.com/docs/api.html#model_Model.distinct
2866
3013
  distinct(field, conditions = {}) {
2867
3014
  return this.mongooseModel.distinct(field, conditions);
@@ -2872,6 +3019,39 @@ var Model = class {
2872
3019
  };
2873
3020
  var model_default = Model;
2874
3021
 
3022
+ // src/helpers/apply-update.ts
3023
+ var import_utils15 = require("@web-ts-toolkit/utils");
3024
+ function applyUpdate(target, data, allowedFields) {
3025
+ const wholeFields = /* @__PURE__ */ new Set();
3026
+ const partialContainers = /* @__PURE__ */ new Set();
3027
+ const collectBoundary = (shape, prefix = "") => {
3028
+ for (const [key, value] of Object.entries(shape)) {
3029
+ const path = prefix ? `${prefix}.${key}` : key;
3030
+ if (value === true) wholeFields.add(path);
3031
+ else {
3032
+ partialContainers.add(path);
3033
+ collectBoundary(value, path);
3034
+ }
3035
+ }
3036
+ };
3037
+ for (const field of allowedFields) {
3038
+ collectBoundary((0, import_utils15.set)({}, field, true));
3039
+ }
3040
+ const apply = (values, prefix = "") => {
3041
+ for (const [key, value] of Object.entries(values)) {
3042
+ const path = prefix ? `${prefix}.${key}` : key;
3043
+ if (partialContainers.has(path) && !wholeFields.has(path) && ((0, import_utils15.isPlainObject)(value) || Array.isArray(value))) {
3044
+ apply(value, path);
3045
+ } else if (isDocument2(target)) {
3046
+ target.set(path, value);
3047
+ } else {
3048
+ (0, import_utils15.set)(target, path, value);
3049
+ }
3050
+ }
3051
+ };
3052
+ apply(data ?? {});
3053
+ }
3054
+
2875
3055
  // src/logger-helpers.ts
2876
3056
  var SENSITIVE_FILTER_KEYS = /* @__PURE__ */ new Set([
2877
3057
  "password",
@@ -3050,28 +3230,52 @@ var error = (error2) => {
3050
3230
  };
3051
3231
 
3052
3232
  // src/services/model-subdocument-service.ts
3053
- var import_utils14 = require("@web-ts-toolkit/utils");
3233
+ var import_utils16 = require("@web-ts-toolkit/utils");
3234
+ async function visibleMutationRows(service, isAllowed, parentId, sub, rows, response) {
3235
+ const accesses = ["read", `subs.${sub}.read`];
3236
+ if (response === "create") accesses.push(`subs.${sub}.list`);
3237
+ const allowed = await Promise.all(accesses.map(isAllowed));
3238
+ if (allowed.some((value) => !value)) return [];
3239
+ const parentFilter = await service.genFilter("read", { _id: parentId });
3240
+ if (parentFilter === false) return [];
3241
+ const parent = await service.findRawParentDoc({
3242
+ filter: { $and: [{ _id: parentId }, parentFilter] },
3243
+ select: "_id",
3244
+ populate: [],
3245
+ lean: true
3246
+ });
3247
+ if (!parent) return [];
3248
+ const targetFilter = response === "create" ? {} : response === "single" ? { _id: rows[0]?._id } : { _id: { $in: rows.map((row) => row._id) } };
3249
+ const [readFilter, listFilter, select] = await Promise.all([
3250
+ service.genFilter(`subs.${sub}.read`, targetFilter),
3251
+ response === "create" ? service.genFilter(`subs.${sub}.list`) : Promise.resolve({}),
3252
+ service.genQuerySelect("read", null, false, [sub, "sub"])
3253
+ ]);
3254
+ if (readFilter === false || listFilter === false) return [];
3255
+ const visible = filterCollection(filterCollection(rows, listFilter), readFilter);
3256
+ return select ? visible.map((row) => (0, import_utils16.pick)(toObject(row), select.concat("_id"))) : visible;
3257
+ }
3054
3258
  async function listSub(service, id, sub, options) {
3055
3259
  const { filter: ft, select } = options ?? {};
3056
3260
  const filterErrors = validateClientFilter(ft);
3057
3261
  if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
3058
3262
  const parentDoc = await getParentDoc(service, id, sub, null, { access: "read" });
3059
3263
  if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
3060
- let result = (0, import_utils14.get)(parentDoc, sub);
3264
+ let result = (0, import_utils16.get)(parentDoc, sub);
3061
3265
  const [subFilter, subSelect] = await Promise.all([
3062
3266
  service.genFilter(`subs.${sub}.list`, ft),
3063
3267
  service.genQuerySelect("list", select, false, [sub, "sub"])
3064
3268
  ]);
3065
3269
  if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
3066
3270
  result = filterCollection(result, subFilter);
3067
- if (subSelect) result = result.map((v) => (0, import_utils14.pick)(toObject(v), subSelect.concat("_id")));
3271
+ if (subSelect) result = result.map((v) => (0, import_utils16.pick)(toObject(v), subSelect.concat("_id")));
3068
3272
  return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length };
3069
3273
  }
3070
3274
  async function readSub(service, id, sub, subId, options) {
3071
3275
  const { select, populate } = options ?? {};
3072
3276
  const parentDoc = await getParentDoc(service, id, sub, { populate }, { access: "read" });
3073
3277
  if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
3074
- const result = (0, import_utils14.get)(parentDoc, sub);
3278
+ const result = (0, import_utils16.get)(parentDoc, sub);
3075
3279
  const [subFilter, subSelect] = await Promise.all([
3076
3280
  service.genFilter(`subs.${sub}.read`, { _id: subId }),
3077
3281
  service.genQuerySelect("read", select, false, [sub, "sub"])
@@ -3079,28 +3283,27 @@ async function readSub(service, id, sub, subId, options) {
3079
3283
  if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
3080
3284
  let subdoc = findElement(result, subFilter);
3081
3285
  if (!subdoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
3082
- if (subSelect) subdoc = (0, import_utils14.pick)(toObject(subdoc), subSelect.concat(["_id"]));
3286
+ if (subSelect) subdoc = (0, import_utils16.pick)(toObject(subdoc), subSelect.concat(["_id"]));
3083
3287
  return { success: true, kind: "single", code: "success" /* Success */, data: subdoc };
3084
3288
  }
3085
- async function updateSub(service, id, sub, subId, data) {
3289
+ async function updateSub(service, persist, isAllowed, id, sub, subId, data) {
3086
3290
  const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
3087
3291
  if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
3088
- const result = (0, import_utils14.get)(parentDoc, sub);
3089
- const [subFilter, subReadSelect, subUpdateSelect] = await Promise.all([
3292
+ const result = (0, import_utils16.get)(parentDoc, sub);
3293
+ const [subFilter, subUpdateSelect] = await Promise.all([
3090
3294
  service.genFilter(`subs.${sub}.update`, { _id: subId }),
3091
- service.genQuerySelect("read", null, false, [sub, "sub"]),
3092
3295
  service.genQuerySelect("update", null, false, [sub, "sub"])
3093
3296
  ]);
3094
3297
  if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
3095
- let subdoc = findElement(result, subFilter);
3298
+ const subdoc = findElement(result, subFilter);
3096
3299
  if (!subdoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
3097
- const allowedData = (0, import_utils14.pick)(data, subUpdateSelect);
3098
- Object.assign(subdoc, allowedData);
3099
- await parentDoc.save();
3100
- if (subReadSelect) subdoc = (0, import_utils14.pick)(toObject(subdoc), subReadSelect.concat(["_id"]));
3101
- return { success: true, kind: "single", code: "success" /* Success */, data: subdoc };
3300
+ const allowedData = (0, import_utils16.pick)(data, subUpdateSelect);
3301
+ applyUpdate(subdoc, allowedData, subUpdateSelect);
3302
+ await persist(() => parentDoc.save());
3303
+ const visible = await visibleMutationRows(service, isAllowed, parentDoc._id, sub, [subdoc], "single");
3304
+ return { success: true, kind: "single", code: "success" /* Success */, data: visible[0] ?? null };
3102
3305
  }
3103
- async function bulkUpdateSub(service, id, sub, data) {
3306
+ async function bulkUpdateSub(service, persist, isAllowed, id, sub, data) {
3104
3307
  const { maxBulkItems } = service.getRequestComplexity();
3105
3308
  if (data.length > maxBulkItems) {
3106
3309
  return {
@@ -3112,25 +3315,24 @@ async function bulkUpdateSub(service, id, sub, data) {
3112
3315
  }
3113
3316
  const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
3114
3317
  if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
3115
- let result = (0, import_utils14.get)(parentDoc, sub);
3116
- const [subFilter, subReadSelect, subUpdateSelect] = await Promise.all([
3318
+ let result = (0, import_utils16.get)(parentDoc, sub);
3319
+ const [subFilter, subUpdateSelect] = await Promise.all([
3117
3320
  service.genFilter(`subs.${sub}.update`, { _id: { $in: data.map((v) => v._id) } }),
3118
- service.genQuerySelect("read", null, false, [sub, "sub"]),
3119
3321
  service.genQuerySelect("update", null, false, [sub, "sub"])
3120
3322
  ]);
3121
3323
  if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
3122
- result = filterCollection(result, subFilter);
3123
- (0, import_utils14.forEach)(result, (subdoc) => {
3324
+ result = filterCollection(result, subFilter).filter((subdoc) => findElementById(data, subdoc._id));
3325
+ (0, import_utils16.forEach)(result, (subdoc) => {
3124
3326
  const tdata = findElementById(data, subdoc._id);
3125
3327
  if (!tdata) return;
3126
- const allowedData = (0, import_utils14.pick)(tdata, subUpdateSelect);
3127
- Object.assign(subdoc, allowedData);
3328
+ const allowedData = (0, import_utils16.pick)(tdata, subUpdateSelect);
3329
+ applyUpdate(subdoc, allowedData, subUpdateSelect);
3128
3330
  });
3129
- await parentDoc.save();
3130
- if (subReadSelect) result = result.map((v) => (0, import_utils14.pick)(toObject(v), subReadSelect.concat(["_id"])));
3331
+ await persist(() => parentDoc.save());
3332
+ result = await visibleMutationRows(service, isAllowed, parentDoc._id, sub, result, "bulk");
3131
3333
  return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length };
3132
3334
  }
3133
- async function createSub(service, id, sub, data, options) {
3335
+ async function createSub(service, persist, isAllowed, id, sub, data, options) {
3134
3336
  const { addFirst } = options ?? {};
3135
3337
  const { maxBulkItems } = service.getRequestComplexity();
3136
3338
  if (Array.isArray(data) && data.length > maxBulkItems) {
@@ -3143,31 +3345,28 @@ async function createSub(service, id, sub, data, options) {
3143
3345
  }
3144
3346
  const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
3145
3347
  if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
3146
- let result = (0, import_utils14.get)(parentDoc, sub);
3147
- const [subCreateSelect, subReadSelect] = await Promise.all([
3148
- service.genQuerySelect("create", null, false, [sub, "sub"]),
3149
- service.genQuerySelect("read", null, false, [sub, "sub"])
3150
- ]);
3151
- const allowedData = Array.isArray(data) ? data.map((row) => (0, import_utils14.pick)(row, subCreateSelect)) : (0, import_utils14.pick)(data, subCreateSelect);
3348
+ let result = (0, import_utils16.get)(parentDoc, sub);
3349
+ const subCreateSelect = await service.genQuerySelect("create", null, false, [sub, "sub"]);
3350
+ const allowedData = Array.isArray(data) ? data.map((row) => (0, import_utils16.pick)(row, subCreateSelect)) : (0, import_utils16.pick)(data, subCreateSelect);
3152
3351
  if (Array.isArray(allowedData)) {
3153
3352
  addFirst === true ? result.unshift(...allowedData) : result.push(...allowedData);
3154
3353
  } else {
3155
3354
  addFirst === true ? result.unshift(allowedData) : result.push(allowedData);
3156
3355
  }
3157
- await parentDoc.save();
3158
- if (subReadSelect) result = result.map((v) => (0, import_utils14.pick)(toObject(v), subReadSelect.concat(["_id"])));
3356
+ await persist(() => parentDoc.save());
3357
+ result = await visibleMutationRows(service, isAllowed, parentDoc._id, sub, result, "create");
3159
3358
  return { success: true, kind: "list", code: "created" /* Created */, data: result, count: result.length };
3160
3359
  }
3161
- async function deleteSub(service, id, sub, subId) {
3360
+ async function deleteSub(service, persist, id, sub, subId) {
3162
3361
  const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
3163
3362
  if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
3164
- const result = (0, import_utils14.get)(parentDoc, sub);
3363
+ const result = (0, import_utils16.get)(parentDoc, sub);
3165
3364
  const subFilter = await service.genFilter(`subs.${sub}.delete`, { _id: subId });
3166
3365
  if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
3167
3366
  const subdoc = findElement(result, subFilter);
3168
3367
  if (!subdoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
3169
3368
  await ("deleteOne" in subdoc ? subdoc.deleteOne?.() : subdoc.remove?.());
3170
- await parentDoc.save();
3369
+ await persist(() => parentDoc.save());
3171
3370
  return { success: true, kind: "single", code: "success" /* Success */, data: subdoc._id };
3172
3371
  }
3173
3372
  async function getParentDoc(service, id, sub, args, options) {
@@ -3326,6 +3525,15 @@ var shouldKeepCorrelatedRef = (ref, keep) => {
3326
3525
  return false;
3327
3526
  };
3328
3527
  var Service = class extends Base {
3528
+ constructor(req, modelName) {
3529
+ super(req, modelName);
3530
+ this.persist = (operation) => this.getCorrelatedExecState().scheduler.work(operation);
3531
+ this.model = admitModelPersistence(this.createModelAdapter(modelName), this.getCorrelatedExecState().scheduler);
3532
+ this.options = this.getModelRouterOptions(modelName);
3533
+ this.defaults = this.options.defaults || {};
3534
+ this.baseFields = ["_id"];
3535
+ this.baseFieldsExt = this.baseFields.concat(this.options.documentPermissionField);
3536
+ }
3329
3537
  findRawParentDoc(args) {
3330
3538
  return this.model.findOne({
3331
3539
  ...args,
@@ -3342,6 +3550,10 @@ var Service = class extends Base {
3342
3550
  asServiceHookContext(context) {
3343
3551
  return context;
3344
3552
  }
3553
+ getEffectiveAllowedSortFields(allowedSortFields) {
3554
+ const extra = this.options?.sortableFields ?? [];
3555
+ return extra.length > 0 ? [...allowedSortFields, ...extra] : allowedSortFields;
3556
+ }
3345
3557
  beginOp(op, filter2, extra) {
3346
3558
  const startedAt = Date.now();
3347
3559
  debug({
@@ -3363,14 +3575,6 @@ var Service = class extends Base {
3363
3575
  ...extra ?? {}
3364
3576
  });
3365
3577
  }
3366
- constructor(req, modelName) {
3367
- super(req, modelName);
3368
- this.model = this.createModelAdapter(modelName);
3369
- this.options = this.getModelRouterOptions(modelName);
3370
- this.defaults = this.options.defaults || {};
3371
- this.baseFields = ["_id"];
3372
- this.baseFieldsExt = this.baseFields.concat(this.options.documentPermissionField);
3373
- }
3374
3578
  async findOne(filter2, args, options) {
3375
3579
  const filterErrors = this.validateClientFilter(filter2);
3376
3580
  if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
@@ -3378,30 +3582,36 @@ var Service = class extends Base {
3378
3582
  const { skim, includePermissions, access, populateAccess, lean } = this.resolveFindOneOptions(options);
3379
3583
  const { filter: overrideFilter, select: overrideSelect, populate: overridePopulate } = overrides ?? {};
3380
3584
  let parsedFilter;
3585
+ let processedInclude;
3381
3586
  try {
3382
- parsedFilter = await this.parseClientData(filter2);
3587
+ processedInclude = this.processInclude(include);
3588
+ parsedFilter = overrideFilter === false ? false : await this.parseClientData(filter2);
3383
3589
  } catch (error2) {
3384
3590
  const result = this.getClientRequestErrorResult(error2);
3385
3591
  if (result) return result;
3386
3592
  throw error2;
3387
3593
  }
3388
3594
  let [_filter, _select, _populate, allowedSortFields] = await Promise.all([
3389
- overrideFilter || this.genFilter(access, parsedFilter),
3595
+ overrideFilter ?? this.genFilter(access, parsedFilter),
3390
3596
  overrideSelect || this.genQuerySelect(access, select),
3391
3597
  overridePopulate || this.genPopulate(populateAccess || access, populate),
3392
3598
  this.genAllowedFields({}, access, this.baseFieldsExt)
3393
3599
  ]);
3394
- const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } = this.processInclude(include);
3600
+ const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } = processedInclude;
3395
3601
  const correlatedOutputPaths = correlatedIncludes.map((entry) => entry.path);
3396
3602
  const finalSelect = normalizeSelect(_select).concat(includeLocalFields, correlatedReferenceFields);
3603
+ const effectiveAllowedSortFields = this.getEffectiveAllowedSortFields(allowedSortFields);
3604
+ const stripDisallowedSort = this.options?.stripDisallowedSort ?? false;
3605
+ const sanitizedSort = stripDisallowedSort ? sanitizeSortFields(sort, effectiveAllowedSortFields) : null;
3606
+ const effectiveSort = stripDisallowedSort ? sanitizedSort?.sort : sort;
3397
3607
  const query = {
3398
3608
  filter: _filter,
3399
3609
  select: finalSelect,
3400
- sort,
3610
+ sort: effectiveSort,
3401
3611
  populate: _populate
3402
3612
  };
3403
3613
  const startedAt = this.beginOp("findOne", _filter, {
3404
- sort,
3614
+ sort: effectiveSort,
3405
3615
  selectCount: finalSelect.length,
3406
3616
  populateCount: Array.isArray(_populate) ? _populate.length : _populate ? 1 : 0
3407
3617
  });
@@ -3409,10 +3619,17 @@ var Service = class extends Base {
3409
3619
  this.completeOp("findOne", startedAt, "forbidden" /* Forbidden */, _filter);
3410
3620
  return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
3411
3621
  }
3412
- const sortErrors = validateSortFields(sort, allowedSortFields);
3413
- if (sortErrors.length > 0) {
3414
- this.completeOp("findOne", startedAt, "bad_request" /* BadRequest */, _filter);
3415
- return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: sortErrors, query };
3622
+ if (stripDisallowedSort) {
3623
+ if (sanitizedSort && sanitizedSort.errors.length > 0) {
3624
+ this.completeOp("findOne", startedAt, "bad_request" /* BadRequest */, _filter);
3625
+ return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: sanitizedSort.errors, query };
3626
+ }
3627
+ } else {
3628
+ const sortErrors = validateSortFields(sort, effectiveAllowedSortFields);
3629
+ if (sortErrors.length > 0) {
3630
+ this.completeOp("findOne", startedAt, "bad_request" /* BadRequest */, _filter);
3631
+ return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: sortErrors, query };
3632
+ }
3416
3633
  }
3417
3634
  let doc = await this.model.findOne({ ...query, lean });
3418
3635
  if (!doc) {
@@ -3426,7 +3643,7 @@ var Service = class extends Base {
3426
3643
  originalDocumentSnapshot: toObject(doc),
3427
3644
  resolvedQuery: query
3428
3645
  };
3429
- const correlatedSnapshots = [(0, import_utils15.cloneDeep)(toObject(doc))];
3646
+ const correlatedSnapshots = [(0, import_utils17.cloneDeep)(toObject(doc))];
3430
3647
  try {
3431
3648
  doc = await this.includeDocs(doc, includes);
3432
3649
  if (correlatedIncludes.length > 0) {
@@ -3473,7 +3690,8 @@ var Service = class extends Base {
3473
3690
  const { select, populate, include, overrides } = this.resolveFindByIdArgs(args);
3474
3691
  const { skim, includePermissions, access, populateAccess, lean } = this.resolveFindByIdOptions(options);
3475
3692
  const { select: overrideSelect, populate: overridePopulate, idFilter: overrideIdFilter } = overrides ?? {};
3476
- const filter2 = overrideIdFilter || await this.genIDFilter(id);
3693
+ const filter2 = overrideIdFilter ?? await this.genIDFilter(id);
3694
+ if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query: { filter: filter2 } };
3477
3695
  return this.findOne(
3478
3696
  filter2,
3479
3697
  {
@@ -3495,33 +3713,39 @@ var Service = class extends Base {
3495
3713
  const { skim, includePermissions, includeCount, populateAccess, lean } = this.resolveFindOptions(options);
3496
3714
  const { filter: overrideFilter, select: overrideSelect, populate: overridePopulate } = overrides ?? {};
3497
3715
  let parsedFilter;
3716
+ let processedInclude;
3498
3717
  try {
3499
- parsedFilter = await this.parseClientData(filter2);
3718
+ processedInclude = this.processInclude(include);
3719
+ parsedFilter = overrideFilter === false ? false : await this.parseClientData(filter2);
3500
3720
  } catch (error2) {
3501
3721
  const result = this.getClientRequestErrorResult(error2);
3502
3722
  if (result) return result;
3503
3723
  throw error2;
3504
3724
  }
3505
3725
  const [_filter, _select, _populate, pagination, allowedSortFields] = await Promise.all([
3506
- overrideFilter || this.genFilter("list", parsedFilter),
3726
+ overrideFilter ?? this.genFilter("list", parsedFilter),
3507
3727
  overrideSelect || this.genQuerySelect("list", select),
3508
3728
  overridePopulate || this.genPopulate(populateAccess, populate),
3509
3729
  genPagination({ skip, limit, page, pageSize }, this.options.listHardLimit),
3510
3730
  this.genAllowedFields({}, "list", this.baseFieldsExt)
3511
3731
  ]);
3512
3732
  const finalSelect = normalizeSelect(_select);
3513
- const filteredPopulate = (0, import_utils15.isArray)(finalSelect) && (0, import_utils15.isArray)(_populate) ? _populate.filter((p) => finalSelect.includes(p.path.split(".")[0])) : _populate;
3514
- const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } = this.processInclude(include);
3733
+ const filteredPopulate = (0, import_utils17.isArray)(finalSelect) && (0, import_utils17.isArray)(_populate) ? _populate.filter((p) => finalSelect.includes(p.path.split(".")[0])) : _populate;
3734
+ const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } = processedInclude;
3515
3735
  const correlatedOutputPaths = correlatedIncludes.map((entry) => entry.path);
3736
+ const effectiveAllowedSortFields = this.getEffectiveAllowedSortFields(allowedSortFields);
3737
+ const stripDisallowedSort = this.options?.stripDisallowedSort ?? false;
3738
+ const sanitizedSort = stripDisallowedSort ? sanitizeSortFields(sort, effectiveAllowedSortFields) : null;
3739
+ const effectiveSort = stripDisallowedSort ? sanitizedSort?.sort : sort;
3516
3740
  const query = {
3517
3741
  filter: _filter,
3518
3742
  select: finalSelect.concat(includeLocalFields, correlatedReferenceFields),
3519
3743
  populate: filteredPopulate,
3520
- sort,
3744
+ sort: effectiveSort,
3521
3745
  ...pagination
3522
3746
  };
3523
3747
  const startedAt = this.beginOp("find", _filter, {
3524
- sort,
3748
+ sort: effectiveSort,
3525
3749
  skip: pagination.skip,
3526
3750
  limit: pagination.limit,
3527
3751
  selectCount: finalSelect.concat(includeLocalFields, correlatedReferenceFields).length,
@@ -3531,10 +3755,17 @@ var Service = class extends Base {
3531
3755
  this.completeOp("find", startedAt, "forbidden" /* Forbidden */, _filter);
3532
3756
  return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
3533
3757
  }
3534
- const sortErrors = validateSortFields(sort, allowedSortFields);
3535
- if (sortErrors.length > 0) {
3536
- this.completeOp("find", startedAt, "bad_request" /* BadRequest */, _filter);
3537
- return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: sortErrors, query };
3758
+ if (stripDisallowedSort) {
3759
+ if (sanitizedSort && sanitizedSort.errors.length > 0) {
3760
+ this.completeOp("find", startedAt, "bad_request" /* BadRequest */, _filter);
3761
+ return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: sanitizedSort.errors, query };
3762
+ }
3763
+ } else {
3764
+ const sortErrors = validateSortFields(sort, effectiveAllowedSortFields);
3765
+ if (sortErrors.length > 0) {
3766
+ this.completeOp("find", startedAt, "bad_request" /* BadRequest */, _filter);
3767
+ return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: sortErrors, query };
3768
+ }
3538
3769
  }
3539
3770
  let docs = await this.model.find({
3540
3771
  ...query,
@@ -3548,8 +3779,8 @@ var Service = class extends Base {
3548
3779
  originalDocumentSnapshot: toObject(doc),
3549
3780
  resolvedQuery: query
3550
3781
  }));
3551
- const _decorate = (0, import_utils15.isFunction)(decorate) ? decorate : (v) => v;
3552
- const correlatedSnapshots = docs.map((doc) => (0, import_utils15.cloneDeep)(toObject(doc)));
3782
+ const _decorate = (0, import_utils17.isFunction)(decorate) ? decorate : (v) => v;
3783
+ const correlatedSnapshots = docs.map((doc) => (0, import_utils17.cloneDeep)(toObject(doc)));
3553
3784
  try {
3554
3785
  docs = await this.includeDocs(docs, includes);
3555
3786
  if (correlatedIncludes.length > 0) {
@@ -3665,16 +3896,16 @@ var Service = class extends Base {
3665
3896
  resolvedQuery: resolvedPopulate.length > 0 ? { populate: resolvedPopulate } : {}
3666
3897
  };
3667
3898
  const allowedFields = await this.genAllowedFields(item, "create");
3668
- const allowedData = (0, import_utils15.pick)(item, allowedFields);
3899
+ const allowedData = (0, import_utils17.pick)(item, allowedFields);
3669
3900
  context.allowedFields = allowedFields;
3670
3901
  context.allowedData = allowedData;
3671
3902
  const validated = await this.validate(allowedData, "create", context);
3672
- if ((0, import_utils15.isBoolean)(validated)) {
3903
+ if ((0, import_utils17.isBoolean)(validated)) {
3673
3904
  if (!validated) {
3674
3905
  validationErrors.push({ index, errors: [] });
3675
3906
  return void 0;
3676
3907
  }
3677
- } else if ((0, import_utils15.isArray)(validated)) {
3908
+ } else if ((0, import_utils17.isArray)(validated)) {
3678
3909
  if (validated.length > 0) {
3679
3910
  validationErrors.push({
3680
3911
  index,
@@ -3704,7 +3935,7 @@ var Service = class extends Base {
3704
3935
  contexts[index].preparedData = preparedData;
3705
3936
  return preparedData;
3706
3937
  });
3707
- const _decorate = (0, import_utils15.isFunction)(decorate) ? decorate : (v) => v;
3938
+ const _decorate = (0, import_utils17.isFunction)(decorate) ? decorate : (v) => v;
3708
3939
  const createdDocs = await this.model.create(items);
3709
3940
  const docs = await mapWithConcurrencyLimit(createdDocs, maxBulkConcurrency, async (doc, index) => {
3710
3941
  contexts[index].currentDocument = doc;
@@ -3721,7 +3952,7 @@ var Service = class extends Base {
3721
3952
  }
3722
3953
  if (includeDocPermissions) doc = await this.addDocPermissions(doc, "create", contexts[index]);
3723
3954
  if (includePermissions) doc = await this.addFieldPermissions(doc, "read", contexts[index]);
3724
- if (resolvedPopulate.length > 0) await populateDoc(doc, resolvedPopulate);
3955
+ if (resolvedPopulate.length > 0) await this.persist(() => populateDoc(doc, resolvedPopulate));
3725
3956
  doc = await this.trimOutputFields(doc, "read", this.baseFieldsExt);
3726
3957
  let outputDoc = await _decorate(doc, contexts[index]);
3727
3958
  if (!includePermissions) outputDoc = this.addEmptyPermissions(outputDoc);
@@ -3777,6 +4008,13 @@ var Service = class extends Base {
3777
4008
  data: doc
3778
4009
  };
3779
4010
  }
4011
+ /**
4012
+ * Update at authorized policy paths: admitted leaves preserve omitted siblings;
4013
+ * whole-authorized objects/arrays replace. Shared by updateById and existing-row
4014
+ * upsert. Validation precedes trusted prepare output (not re-filtered); partial
4015
+ * prepare containers preserve omissions. Transform retains explicit document-setter
4016
+ * replacement authority. Send nested client JSON, not literal dotted update keys.
4017
+ */
3780
4018
  async updateOne(filter2, data, args, options, decorate) {
3781
4019
  const filterErrors = this.validateClientFilter(filter2);
3782
4020
  if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
@@ -3784,7 +4022,7 @@ var Service = class extends Base {
3784
4022
  const { skim, includePermissions, populateAccess } = this.resolveUpdateOneOptions(options);
3785
4023
  const { filter: overrideFilter, populate: overridePopulate } = overrides ?? {};
3786
4024
  const [_filter, _populate] = await Promise.all([
3787
- overrideFilter || this.genFilter("update", filter2),
4025
+ overrideFilter ?? this.genFilter("update", filter2),
3788
4026
  overridePopulate || this.genPopulate(populateAccess, populate)
3789
4027
  ]);
3790
4028
  const query = { filter: _filter, populate: _populate };
@@ -3822,16 +4060,16 @@ var Service = class extends Base {
3822
4060
  context.docPermissions = this.getDocPermissions(doc);
3823
4061
  context.currentDocument = doc;
3824
4062
  const allowedFields = await this.genAllowedFields(doc, "update");
3825
- const allowedData = (0, import_utils15.pick)(data, allowedFields);
4063
+ const allowedData = (0, import_utils17.pick)(data, allowedFields);
3826
4064
  context.allowedFields = allowedFields;
3827
4065
  context.allowedData = allowedData;
3828
4066
  const validated = await this.validate(allowedData, "update", context);
3829
- if ((0, import_utils15.isBoolean)(validated)) {
4067
+ if ((0, import_utils17.isBoolean)(validated)) {
3830
4068
  if (!validated) {
3831
4069
  this.completeOp("updateOne", startedAt, "bad_request" /* BadRequest */, _filter);
3832
4070
  return { success: false, kind: "error", code: "bad_request" /* BadRequest */ };
3833
4071
  }
3834
- } else if ((0, import_utils15.isArray)(validated)) {
4072
+ } else if ((0, import_utils17.isArray)(validated)) {
3835
4073
  if (validated.length > 0) {
3836
4074
  this.completeOp("updateOne", startedAt, "bad_request" /* BadRequest */, _filter);
3837
4075
  return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: validated };
@@ -3839,18 +4077,18 @@ var Service = class extends Base {
3839
4077
  }
3840
4078
  const prepared = await this.prepare(allowedData, "update", context);
3841
4079
  context.preparedData = prepared;
3842
- Object.assign(doc, prepared);
4080
+ applyUpdate(doc, prepared, allowedFields);
3843
4081
  context.modifiedPaths = doc.modifiedPaths();
3844
4082
  doc = assertModelDocument(await this.transform(doc, "update", context), this.modelName, "transform");
3845
4083
  context.currentDocument = doc;
3846
- doc = await doc.save();
4084
+ doc = await this.persist(() => doc.save());
3847
4085
  const diffExcludeFields = [this.options.documentPermissionField, "__v"];
3848
4086
  this.asServiceHookContext(context).diff = (d) => {
3849
4087
  context.changes = (0, import_just_diff.diff)(
3850
- (0, import_utils15.omit)(context.originalDocumentSnapshot, diffExcludeFields),
3851
- (0, import_utils15.omit)(d.toObject({ virtuals: false }), diffExcludeFields)
4088
+ (0, import_utils17.omit)(context.originalDocumentSnapshot, diffExcludeFields),
4089
+ (0, import_utils17.omit)(d.toObject({ virtuals: false }), diffExcludeFields)
3852
4090
  ) || [];
3853
- context.modifiedPaths = (0, import_utils15.uniq)(context.changes.map((di) => di.path.length > 0 ? String(di.path[0]) : ""));
4091
+ context.modifiedPaths = (0, import_utils17.uniq)(context.changes.map((di) => di.path.length > 0 ? String(di.path[0]) : ""));
3854
4092
  };
3855
4093
  doc = assertModelDocument(await this.afterPersist(doc, "update", context), this.modelName, "afterPersist");
3856
4094
  context.currentDocument = doc;
@@ -3863,10 +4101,10 @@ var Service = class extends Base {
3863
4101
  }
3864
4102
  if (includeDocPermissions) doc = await this.addDocPermissions(doc, "update", context);
3865
4103
  if (includePermissions) doc = await this.addFieldPermissions(doc, "update", context);
3866
- if (_populate) await populateDoc(doc, _populate);
4104
+ if (_populate) await this.persist(() => populateDoc(doc, _populate));
3867
4105
  doc = await this.trimOutputFields(doc, "read", this.baseFieldsExt);
3868
4106
  let outputDoc = doc;
3869
- if ((0, import_utils15.isFunction)(decorate)) outputDoc = await decorate(outputDoc, context);
4107
+ if ((0, import_utils17.isFunction)(decorate)) outputDoc = await decorate(outputDoc, context);
3870
4108
  if (!includePermissions) outputDoc = this.addEmptyPermissions(outputDoc);
3871
4109
  this.completeOp("updateOne", startedAt, "success" /* Success */, _filter);
3872
4110
  return { success: true, kind: "single", code: "success" /* Success */, data: outputDoc, input: prepared };
@@ -3875,7 +4113,8 @@ var Service = class extends Base {
3875
4113
  const { populate, overrides } = this.resolveUpdateByIdArgs(args);
3876
4114
  const { skim, includePermissions, populateAccess } = this.resolveUpdateByIdOptions(options);
3877
4115
  const { populate: overridePopulate, idFilter: overrideIdFilter } = overrides;
3878
- const filter2 = overrideIdFilter || await this.genIDFilter(id);
4116
+ const filter2 = overrideIdFilter ?? await this.genIDFilter(id);
4117
+ if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query: { filter: filter2 } };
3879
4118
  return this.updateOne(
3880
4119
  filter2,
3881
4120
  data,
@@ -3895,7 +4134,7 @@ var Service = class extends Base {
3895
4134
  const { populate, overrides } = this.resolveUpsertArgs(args);
3896
4135
  const { skim, includePermissions, populateAccess } = this.resolveUpsertOptions(options);
3897
4136
  const { filter: overrideFilter, populate: overridePopulate } = overrides ?? {};
3898
- const _filter = await (overrideFilter || this.genFilter("update", filter2));
4137
+ const _filter = await (overrideFilter ?? this.genFilter("update", filter2));
3899
4138
  const query = { filter: _filter };
3900
4139
  const startedAt = this.beginOp("upsert", _filter);
3901
4140
  if (_filter === false) {
@@ -3955,7 +4194,9 @@ var Service = class extends Base {
3955
4194
  resolvedQuery: query
3956
4195
  };
3957
4196
  await this.beforeDelete(doc, context);
3958
- await ("deleteOne" in doc ? doc.deleteOne() : doc.remove());
4197
+ await this.persist(
4198
+ () => "deleteOne" in doc ? doc.deleteOne() : doc.remove()
4199
+ );
3959
4200
  context.finalDocumentSnapshot = toObject(doc);
3960
4201
  await this.afterDelete(doc, context);
3961
4202
  this.completeOp("delete", startedAt, "success" /* Success */, filter2);
@@ -3966,6 +4207,7 @@ var Service = class extends Base {
3966
4207
  if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
3967
4208
  const { access, includeId } = this.resolveExistsOptions(options);
3968
4209
  filter2 = await this.genFilter(access, filter2);
4210
+ if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query: { filter: filter2 } };
3969
4211
  const result = await this.model.exists(filter2);
3970
4212
  return {
3971
4213
  success: true,
@@ -4035,6 +4277,13 @@ var Service = class extends Base {
4035
4277
  query
4036
4278
  };
4037
4279
  }
4280
+ /**
4281
+ * Group target document IDs by original parent-key aliases using one aggregate.
4282
+ * Resolves the requested row policy (legacy count includes pass `count`), casts
4283
+ * authorized filters/foreign operands with the target schema, and deduplicates IDs
4284
+ * independently of pagination. Cast failures return BadRequest; false policy returns
4285
+ * Forbidden. Casting does not execute countDocuments middleware/plugins.
4286
+ */
4038
4287
  async countByFieldValues(foreignField, values, filter2 = {}, access = "list") {
4039
4288
  if (!isValidFieldPath(foreignField)) {
4040
4289
  return {
@@ -4046,14 +4295,32 @@ var Service = class extends Base {
4046
4295
  }
4047
4296
  const filterErrors = this.validateClientFilter(filter2);
4048
4297
  if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
4049
- const uniqueValues = (0, import_utils15.uniqBy)(values, (value) => String(value));
4298
+ const uniqueValues = (0, import_utils17.uniqBy)(values, (value) => String(value));
4050
4299
  filter2 = await this.genFilter(access, {
4051
4300
  ...filter2,
4052
4301
  [foreignField]: { $in: uniqueValues }
4053
4302
  });
4054
4303
  const query = { filter: filter2 };
4055
4304
  if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
4056
- const matchFilter = filter2;
4305
+ let matchFilter = filter2;
4306
+ let castValues = uniqueValues;
4307
+ try {
4308
+ if (this.model.castFilter) {
4309
+ matchFilter = this.model.castFilter(matchFilter);
4310
+ const foreignFilter = this.model.castFilter({ [foreignField]: { $in: uniqueValues } });
4311
+ castValues = foreignFilter[foreignField]?.$in;
4312
+ if (!Array.isArray(castValues) || castValues.length !== uniqueValues.length) {
4313
+ throw new Error("Foreign field query casting must retain the join operands");
4314
+ }
4315
+ }
4316
+ } catch {
4317
+ return {
4318
+ success: false,
4319
+ kind: "error",
4320
+ code: "bad_request" /* BadRequest */,
4321
+ errors: [{ detail: "Invalid grouped include count filter or foreign-key value" }]
4322
+ };
4323
+ }
4057
4324
  const rows = await this.model.aggregate([
4058
4325
  { $match: matchFilter },
4059
4326
  {
@@ -4064,12 +4331,20 @@ var Service = class extends Base {
4064
4331
  }
4065
4332
  },
4066
4333
  { $unwind: "$foreignValues" },
4067
- { $match: { foreignValues: { $in: uniqueValues } } },
4334
+ { $match: { foreignValues: { $in: castValues } } },
4068
4335
  { $group: { _id: "$foreignValues", documentIds: { $addToSet: "$_id" } } }
4069
4336
  ]);
4070
- const counts = /* @__PURE__ */ new Map();
4337
+ const grouped = /* @__PURE__ */ new Map();
4071
4338
  for (const row of rows) {
4072
- counts.set(String(row._id), new Set(row.documentIds.map((id) => String(id))));
4339
+ const key = String(row._id);
4340
+ const ids = grouped.get(key) ?? /* @__PURE__ */ new Set();
4341
+ for (const id of row.documentIds) ids.add(String(id));
4342
+ grouped.set(key, ids);
4343
+ }
4344
+ const counts = /* @__PURE__ */ new Map();
4345
+ for (let i = 0; i < uniqueValues.length; i++) {
4346
+ const ids = grouped.get(String(castValues[i]));
4347
+ if (ids) counts.set(String(uniqueValues[i]), ids);
4073
4348
  }
4074
4349
  return { success: true, kind: "single", code: "success" /* Success */, data: counts, query };
4075
4350
  }
@@ -4122,7 +4397,7 @@ var Service = class extends Base {
4122
4397
  return resolveExistsOptions(this, options);
4123
4398
  }
4124
4399
  async getFieldPermissionAccess(ids) {
4125
- const uniqueIds = (0, import_utils15.compact)((0, import_utils15.uniqBy)(ids, (id) => String(id)).map((id) => String(id)));
4400
+ const uniqueIds = (0, import_utils17.compact)((0, import_utils17.uniqBy)(ids, (id) => String(id)).map((id) => String(id)));
4126
4401
  if (uniqueIds.length === 0) {
4127
4402
  return {
4128
4403
  readIds: /* @__PURE__ */ new Set(),
@@ -4148,17 +4423,60 @@ var Service = class extends Base {
4148
4423
  async readSub(id, sub, subId, options) {
4149
4424
  return readSub(this, id, sub, subId, options);
4150
4425
  }
4426
+ /**
4427
+ * Apply authorized policy-path updates, preserving protected siblings, then save.
4428
+ * Output requires post-save parent read and subdocument read operation/row access;
4429
+ * hidden output is successful `data: null` (direct/root-entry 200), not a failed write.
4430
+ * Visible output uses read fields plus `_id`. Do not retry solely for null output.
4431
+ */
4151
4432
  async updateSub(id, sub, subId, data) {
4152
- return updateSub(this, id, sub, subId, data);
4433
+ return updateSub(
4434
+ this,
4435
+ this.persist,
4436
+ (access) => this.req.macl.isAllowed(this.modelName, access),
4437
+ id,
4438
+ sub,
4439
+ subId,
4440
+ data
4441
+ );
4153
4442
  }
4443
+ /**
4444
+ * Save authorized targeted updates, then return readable targets in stored order.
4445
+ * Requires parent/subdocument read access for output, not subdocument list access;
4446
+ * uses read fields plus `_id`. Hidden output succeeds with `data: [], count: 0`
4447
+ * (direct/root-entry 200); count measures visible rows, not all updated rows.
4448
+ */
4154
4449
  async bulkUpdateSub(id, sub, data) {
4155
- return bulkUpdateSub(this, id, sub, (0, import_utils15.castArray)(data));
4450
+ return bulkUpdateSub(
4451
+ this,
4452
+ this.persist,
4453
+ (access) => this.req.macl.isAllowed(this.modelName, access),
4454
+ id,
4455
+ sub,
4456
+ (0, import_utils17.castArray)(data)
4457
+ );
4156
4458
  }
4459
+ /**
4460
+ * Append/prepend admitted rows, then return the visible full array (including when
4461
+ * input is empty). Output requires post-save parent read and subdocument list AND
4462
+ * read guards/row filters; fields use read projection plus `_id`.
4463
+ * Hidden output succeeds with code `created`, `data: [], count: 0`
4464
+ * (direct/root-entry 201). Visible count/order follow filtered stored rows.
4465
+ * Empty output does not mean the write failed; addFirst is a service option.
4466
+ */
4157
4467
  async createSub(id, sub, data, options) {
4158
- return createSub(this, id, sub, data, options);
4468
+ return createSub(
4469
+ this,
4470
+ this.persist,
4471
+ (access) => this.req.macl.isAllowed(this.modelName, access),
4472
+ id,
4473
+ sub,
4474
+ data,
4475
+ options
4476
+ );
4159
4477
  }
4160
4478
  async deleteSub(id, sub, subId) {
4161
- return deleteSub(this, id, sub, subId);
4479
+ return deleteSub(this, this.persist, id, sub, subId);
4162
4480
  }
4163
4481
  async getParentDoc(id, sub, args, options) {
4164
4482
  return getParentDoc(this, id, sub, args, options);
@@ -4166,7 +4484,7 @@ var Service = class extends Base {
4166
4484
  };
4167
4485
 
4168
4486
  // src/services/public-service.ts
4169
- var import_utils16 = require("@web-ts-toolkit/utils");
4487
+ var import_utils18 = require("@web-ts-toolkit/utils");
4170
4488
  var PublicService = class extends Service {
4171
4489
  async _list(filter2, args, options) {
4172
4490
  const { select, populate, include, sort, skip, limit, page, pageSize, tasks } = this.resolvePublicListArgs(args);
@@ -4205,7 +4523,7 @@ var PublicService = class extends Service {
4205
4523
  let d = toObject(doc);
4206
4524
  d = await this.decorate(d, "create", context);
4207
4525
  d = this.runTasks(d, tasks);
4208
- if (select) d = (0, import_utils16.pick)(d, [...normalizeSelect(select), ...this.baseFieldsExt]);
4526
+ if (select) d = (0, import_utils18.pick)(d, [...normalizeSelect(select), ...this.baseFieldsExt]);
4209
4527
  return d;
4210
4528
  }
4211
4529
  );
@@ -4232,6 +4550,7 @@ var PublicService = class extends Service {
4232
4550
  },
4233
4551
  { skim, includePermissions, access, populateAccess, lean }
4234
4552
  );
4553
+ if (!result.success && (result.code === "bad_request" /* BadRequest */ || result.code === "forbidden" /* Forbidden */)) return result;
4235
4554
  if (tryList && (!result.success || !result.data)) {
4236
4555
  const listAllowed = await this.req.macl.isAllowed(this.modelName, "list");
4237
4556
  if (!listAllowed) {
@@ -4272,6 +4591,7 @@ var PublicService = class extends Service {
4272
4591
  },
4273
4592
  { skim, includePermissions, access, populateAccess, lean }
4274
4593
  );
4594
+ if (!result.success && (result.code === "bad_request" /* BadRequest */ || result.code === "forbidden" /* Forbidden */)) return result;
4275
4595
  if (tryList && (!result.success || !result.data)) {
4276
4596
  const listAllowed = await this.req.macl.isAllowed(this.modelName, "list");
4277
4597
  if (!listAllowed) {
@@ -4310,8 +4630,8 @@ var PublicService = class extends Service {
4310
4630
  let d = toObject(doc);
4311
4631
  d = await this.decorate(d, "update", context);
4312
4632
  d = this.runTasks(d, tasks);
4313
- if (select) d = (0, import_utils16.pick)(d, [...normalizeSelect(select), ...this.baseFieldsExt]);
4314
- else if (!returningAll) d = (0, import_utils16.pick)(d, [...Object.keys(data), "_id"]);
4633
+ if (select) d = (0, import_utils18.pick)(d, [...normalizeSelect(select), ...this.baseFieldsExt]);
4634
+ else if (!returningAll) d = (0, import_utils18.pick)(d, [...Object.keys(data), "_id"]);
4315
4635
  return d;
4316
4636
  }
4317
4637
  );
@@ -4320,6 +4640,11 @@ var PublicService = class extends Service {
4320
4640
  }
4321
4641
  return result;
4322
4642
  }
4643
+ /**
4644
+ * With an _id, check update-policy existence then update: terminal denial returns
4645
+ * Forbidden, an allowed miss remains Unauthorized, and neither creates a row.
4646
+ * Without an _id, use the create path.
4647
+ */
4323
4648
  async _upsert(data, args, options) {
4324
4649
  const idKey = this.getIdentifier();
4325
4650
  if (idKey !== "_id") {
@@ -4433,7 +4758,7 @@ var PublicService = class extends Service {
4433
4758
  };
4434
4759
 
4435
4760
  // src/services/data-service.ts
4436
- var import_utils17 = require("@web-ts-toolkit/utils");
4761
+ var import_utils19 = require("@web-ts-toolkit/utils");
4437
4762
  var DataService = class {
4438
4763
  constructor(req, dataName) {
4439
4764
  this.req = req;
@@ -4455,7 +4780,7 @@ var DataService = class {
4455
4780
  let doc = await findElement(this.data, _filter);
4456
4781
  if (!doc) return { success: false, kind: "error", code: "not_found" /* NotFound */, query };
4457
4782
  doc = await this.trimOutputFields(doc, access);
4458
- if (_select.length > 0) doc = (0, import_utils17.pick)(doc, _select);
4783
+ if (_select.length > 0) doc = (0, import_utils19.pick)(doc, _select);
4459
4784
  return { success: true, kind: "single", code: "success" /* Success */, data: doc, query };
4460
4785
  }
4461
4786
  async findById(id, args, options) {
@@ -4495,13 +4820,13 @@ var DataService = class {
4495
4820
  const totalCount = docs.length;
4496
4821
  if (sort) {
4497
4822
  const normalizedSort = normalizeSortForOrderBy(sort);
4498
- docs = (0, import_utils17.orderBy)(docs, normalizedSort.fields, normalizedSort.orders);
4823
+ docs = (0, import_utils19.orderBy)(docs, normalizedSort.fields, normalizedSort.orders);
4499
4824
  }
4500
4825
  const pagedDocs = docs.slice(query.skip, query.limit && query.skip + query.limit);
4501
4826
  const { maxHookConcurrency } = this.getRequestComplexity();
4502
4827
  const trimmed = await mapWithConcurrencyLimit(pagedDocs, maxHookConcurrency, async (doc) => {
4503
4828
  doc = await this.trimOutputFields(doc, "list");
4504
- if (_select.length > 0) doc = (0, import_utils17.pick)(doc, _select);
4829
+ if (_select.length > 0) doc = (0, import_utils19.pick)(doc, _select);
4505
4830
  return doc;
4506
4831
  });
4507
4832
  if (!hasExplicitLimit && trimmed.length === totalCount) {
@@ -4550,7 +4875,7 @@ var DataService = class {
4550
4875
  };
4551
4876
 
4552
4877
  // src/processors.ts
4553
- var import_utils18 = require("@web-ts-toolkit/utils");
4878
+ var import_utils20 = require("@web-ts-toolkit/utils");
4554
4879
  var UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
4555
4880
  var DEFAULT_MAX_SLICE = 1e3;
4556
4881
  var DEFAULT_MASK_REPLACEMENT = "***";
@@ -4585,10 +4910,10 @@ function resolveLeafParents(obj, src) {
4585
4910
  for (let ind = 0; ind < segs.length - 1; ind += 1) {
4586
4911
  const seg = segs[ind];
4587
4912
  targets = targets.reduce((ret, target) => {
4588
- if (!(0, import_utils18.isPlainObject)(target)) return ret;
4913
+ if (!(0, import_utils20.isPlainObject)(target)) return ret;
4589
4914
  const targetObject = target;
4590
4915
  const next = targetObject[seg];
4591
- if ((0, import_utils18.isArray)(next)) {
4916
+ if ((0, import_utils20.isArray)(next)) {
4592
4917
  for (const item of next) {
4593
4918
  ret.push(item);
4594
4919
  }
@@ -4598,8 +4923,8 @@ function resolveLeafParents(obj, src) {
4598
4923
  }
4599
4924
  const leaf = segs[segs.length - 1];
4600
4925
  const pairs = [];
4601
- (0, import_utils18.forEach)(targets, (target) => {
4602
- if (!(0, import_utils18.isPlainObject)(target)) return;
4926
+ (0, import_utils20.forEach)(targets, (target) => {
4927
+ if (!(0, import_utils20.isPlainObject)(target)) return;
4603
4928
  pairs.push({ parent: target, leaf });
4604
4929
  });
4605
4930
  return pairs;
@@ -4607,8 +4932,8 @@ function resolveLeafParents(obj, src) {
4607
4932
  var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
4608
4933
  const mutable = options.mutable !== false;
4609
4934
  const idField = options.idField ?? "_id";
4610
- const obj = mutable ? docObject : (0, import_utils18.cloneDeep)(docObject);
4611
- (0, import_utils18.forEach)(Array.isArray(operations) ? operations : [], (op) => {
4935
+ const obj = mutable ? docObject : (0, import_utils20.cloneDeep)(docObject);
4936
+ (0, import_utils20.forEach)(Array.isArray(operations) ? operations : [], (op) => {
4612
4937
  if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
4613
4938
  const src = op.src;
4614
4939
  const dest = op.dest;
@@ -4617,19 +4942,19 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
4617
4942
  assertSafePath(dest);
4618
4943
  let targets = [obj];
4619
4944
  const segs = src.split(".");
4620
- (0, import_utils18.forEach)(segs, (seg, ind) => {
4945
+ (0, import_utils20.forEach)(segs, (seg, ind) => {
4621
4946
  if (segs.length === ind + 1) {
4622
- (0, import_utils18.forEach)(targets, (target) => {
4623
- if (!(0, import_utils18.isPlainObject)(target)) return;
4947
+ (0, import_utils20.forEach)(targets, (target) => {
4948
+ if (!(0, import_utils20.isPlainObject)(target)) return;
4624
4949
  const targetObject = target;
4625
4950
  const leaf = targetObject[seg];
4626
4951
  const sourceForDest = leaf;
4627
4952
  let depopulated;
4628
- if ((0, import_utils18.isArray)(leaf)) {
4953
+ if ((0, import_utils20.isArray)(leaf)) {
4629
4954
  if (leaf.length === 0) {
4630
4955
  depopulated = [];
4631
- } else if (leaf.every((item) => (0, import_utils18.isPlainObject)(item))) {
4632
- depopulated = (0, import_utils18.map)(leaf, idField).map((id, i) => {
4956
+ } else if (leaf.every((item) => (0, import_utils20.isPlainObject)(item))) {
4957
+ depopulated = (0, import_utils20.map)(leaf, idField).map((id, i) => {
4633
4958
  if (id === void 0) {
4634
4959
  failMissingId(`${src}[${i}]`);
4635
4960
  }
@@ -4638,8 +4963,8 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
4638
4963
  } else {
4639
4964
  return;
4640
4965
  }
4641
- } else if ((0, import_utils18.isPlainObject)(leaf)) {
4642
- const id = (0, import_utils18.get)(leaf, idField);
4966
+ } else if ((0, import_utils20.isPlainObject)(leaf)) {
4967
+ const id = (0, import_utils20.get)(leaf, idField);
4643
4968
  if (id === void 0) {
4644
4969
  failMissingId(src);
4645
4970
  }
@@ -4647,15 +4972,15 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
4647
4972
  } else {
4648
4973
  return;
4649
4974
  }
4650
- (0, import_utils18.set)(targetObject, dest, sourceForDest);
4651
- (0, import_utils18.set)(targetObject, seg, depopulated);
4975
+ (0, import_utils20.set)(targetObject, dest, sourceForDest);
4976
+ (0, import_utils20.set)(targetObject, seg, depopulated);
4652
4977
  });
4653
4978
  } else {
4654
4979
  targets = targets.reduce((ret, target) => {
4655
- if (!(0, import_utils18.isPlainObject)(target)) return ret;
4980
+ if (!(0, import_utils20.isPlainObject)(target)) return ret;
4656
4981
  const targetObject = target;
4657
4982
  const next = targetObject[seg];
4658
- if ((0, import_utils18.isArray)(next)) {
4983
+ if ((0, import_utils20.isArray)(next)) {
4659
4984
  for (const item of next) {
4660
4985
  ret.push(item);
4661
4986
  }
@@ -4669,8 +4994,8 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
4669
4994
  };
4670
4995
  var copyPaths = (docObject, operations, options = { mutable: true }) => {
4671
4996
  const mutable = options.mutable !== false;
4672
- const obj = mutable ? docObject : (0, import_utils18.cloneDeep)(docObject);
4673
- (0, import_utils18.forEach)(Array.isArray(operations) ? operations : [], (op) => {
4997
+ const obj = mutable ? docObject : (0, import_utils20.cloneDeep)(docObject);
4998
+ (0, import_utils20.forEach)(Array.isArray(operations) ? operations : [], (op) => {
4674
4999
  if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
4675
5000
  const src = op.src;
4676
5001
  const dest = op.dest;
@@ -4678,17 +5003,17 @@ var copyPaths = (docObject, operations, options = { mutable: true }) => {
4678
5003
  assertSafePath(src, "copyPaths");
4679
5004
  assertSafePath(dest, "copyPaths");
4680
5005
  if (src === dest) return;
4681
- (0, import_utils18.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
4682
- if (!(0, import_utils18.hasOwn)(parent, leaf)) return;
4683
- (0, import_utils18.set)(parent, dest, (0, import_utils18.cloneDeep)(parent[leaf]));
5006
+ (0, import_utils20.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
5007
+ if (!(0, import_utils20.hasOwn)(parent, leaf)) return;
5008
+ (0, import_utils20.set)(parent, dest, (0, import_utils20.cloneDeep)(parent[leaf]));
4684
5009
  });
4685
5010
  });
4686
5011
  return obj;
4687
5012
  };
4688
5013
  var movePaths = (docObject, operations, options = { mutable: true }) => {
4689
5014
  const mutable = options.mutable !== false;
4690
- const obj = mutable ? docObject : (0, import_utils18.cloneDeep)(docObject);
4691
- (0, import_utils18.forEach)(Array.isArray(operations) ? operations : [], (op) => {
5015
+ const obj = mutable ? docObject : (0, import_utils20.cloneDeep)(docObject);
5016
+ (0, import_utils20.forEach)(Array.isArray(operations) ? operations : [], (op) => {
4692
5017
  if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
4693
5018
  const src = op.src;
4694
5019
  const dest = op.dest;
@@ -4696,10 +5021,10 @@ var movePaths = (docObject, operations, options = { mutable: true }) => {
4696
5021
  assertSafePath(src, "movePaths");
4697
5022
  assertSafePath(dest, "movePaths");
4698
5023
  if (src === dest) return;
4699
- (0, import_utils18.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
4700
- if (!(0, import_utils18.hasOwn)(parent, leaf)) return;
4701
- const value = (0, import_utils18.cloneDeep)(parent[leaf]);
4702
- (0, import_utils18.set)(parent, dest, value);
5024
+ (0, import_utils20.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
5025
+ if (!(0, import_utils20.hasOwn)(parent, leaf)) return;
5026
+ const value = (0, import_utils20.cloneDeep)(parent[leaf]);
5027
+ (0, import_utils20.set)(parent, dest, value);
4703
5028
  if (dest === leaf || dest.startsWith(`${leaf}.`)) return;
4704
5029
  delete parent[leaf];
4705
5030
  });
@@ -4710,8 +5035,8 @@ var sliceArrays = (docObject, operations, options = { mutable: true, maxSlice: D
4710
5035
  const mutable = options.mutable !== false;
4711
5036
  const maxSlice = options.maxSlice ?? DEFAULT_MAX_SLICE;
4712
5037
  assertNonNegativeSafeInteger(maxSlice, "maxSlice", "sliceArrays");
4713
- const obj = mutable ? docObject : (0, import_utils18.cloneDeep)(docObject);
4714
- (0, import_utils18.forEach)(Array.isArray(operations) ? operations : [], (op) => {
5038
+ const obj = mutable ? docObject : (0, import_utils20.cloneDeep)(docObject);
5039
+ (0, import_utils20.forEach)(Array.isArray(operations) ? operations : [], (op) => {
4715
5040
  if (!op || typeof op.src !== "string") return;
4716
5041
  const src = op.src;
4717
5042
  if (src.length === 0) return;
@@ -4728,15 +5053,15 @@ var sliceArrays = (docObject, operations, options = { mutable: true, maxSlice: D
4728
5053
  assertNonNegativeSafeInteger(op.limit, "limit", "sliceArrays");
4729
5054
  limit = Math.min(op.limit, maxSlice);
4730
5055
  }
4731
- (0, import_utils18.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
5056
+ (0, import_utils20.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
4732
5057
  const leafValue = parent[leaf];
4733
- if (!(0, import_utils18.isArray)(leafValue)) return;
5058
+ if (!(0, import_utils20.isArray)(leafValue)) return;
4734
5059
  const end = typeof limit === "undefined" ? void 0 : skip + limit;
4735
5060
  const window = leafValue.slice(skip, end);
4736
5061
  if (typeof rawDest === "undefined") {
4737
- (0, import_utils18.set)(parent, leaf, window);
5062
+ (0, import_utils20.set)(parent, leaf, window);
4738
5063
  } else {
4739
- (0, import_utils18.set)(parent, rawDest, window);
5064
+ (0, import_utils20.set)(parent, rawDest, window);
4740
5065
  }
4741
5066
  });
4742
5067
  });
@@ -4744,44 +5069,44 @@ var sliceArrays = (docObject, operations, options = { mutable: true, maxSlice: D
4744
5069
  };
4745
5070
  var countPaths = (docObject, operations, options = { mutable: true }) => {
4746
5071
  const mutable = options.mutable !== false;
4747
- const obj = mutable ? docObject : (0, import_utils18.cloneDeep)(docObject);
4748
- (0, import_utils18.forEach)(Array.isArray(operations) ? operations : [], (op) => {
5072
+ const obj = mutable ? docObject : (0, import_utils20.cloneDeep)(docObject);
5073
+ (0, import_utils20.forEach)(Array.isArray(operations) ? operations : [], (op) => {
4749
5074
  if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
4750
5075
  const src = op.src;
4751
5076
  const dest = op.dest;
4752
5077
  if (src.length === 0 || dest.length === 0) return;
4753
5078
  assertSafePath(src, "countPaths");
4754
5079
  assertSafePath(dest, "countPaths");
4755
- (0, import_utils18.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
4756
- if (!(0, import_utils18.hasOwn)(parent, leaf)) return;
5080
+ (0, import_utils20.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
5081
+ if (!(0, import_utils20.hasOwn)(parent, leaf)) return;
4757
5082
  const leafValue = parent[leaf];
4758
5083
  let count;
4759
- if ((0, import_utils18.isArray)(leafValue)) {
5084
+ if ((0, import_utils20.isArray)(leafValue)) {
4760
5085
  count = leafValue.length;
4761
- } else if ((0, import_utils18.isPlainObject)(leafValue)) {
5086
+ } else if ((0, import_utils20.isPlainObject)(leafValue)) {
4762
5087
  count = Object.keys(leafValue).length;
4763
5088
  } else if (leafValue === null || typeof leafValue === "undefined") {
4764
5089
  count = 0;
4765
5090
  } else {
4766
5091
  count = 1;
4767
5092
  }
4768
- (0, import_utils18.set)(parent, dest, count);
5093
+ (0, import_utils20.set)(parent, dest, count);
4769
5094
  });
4770
5095
  });
4771
5096
  return obj;
4772
5097
  };
4773
5098
  var maskPaths = (docObject, operations, options = { mutable: true }) => {
4774
5099
  const mutable = options.mutable !== false;
4775
- const obj = mutable ? docObject : (0, import_utils18.cloneDeep)(docObject);
4776
- (0, import_utils18.forEach)(Array.isArray(operations) ? operations : [], (op) => {
5100
+ const obj = mutable ? docObject : (0, import_utils20.cloneDeep)(docObject);
5101
+ (0, import_utils20.forEach)(Array.isArray(operations) ? operations : [], (op) => {
4777
5102
  if (!op || typeof op.src !== "string") return;
4778
5103
  const src = op.src;
4779
5104
  if (src.length === 0) return;
4780
5105
  assertSafePath(src, "maskPaths");
4781
5106
  const replacement = Object.prototype.hasOwnProperty.call(op, "replacement") ? op.replacement : DEFAULT_MASK_REPLACEMENT;
4782
- (0, import_utils18.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
4783
- if (!(0, import_utils18.hasOwn)(parent, leaf)) return;
4784
- (0, import_utils18.set)(parent, leaf, replacement);
5107
+ (0, import_utils20.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
5108
+ if (!(0, import_utils20.hasOwn)(parent, leaf)) return;
5109
+ (0, import_utils20.set)(parent, leaf, replacement);
4785
5110
  });
4786
5111
  });
4787
5112
  return obj;
@@ -4825,10 +5150,10 @@ var Cache = class {
4825
5150
  };
4826
5151
 
4827
5152
  // src/core-shared.ts
4828
- var import_utils20 = require("@web-ts-toolkit/utils");
5153
+ var import_utils22 = require("@web-ts-toolkit/utils");
4829
5154
 
4830
5155
  // src/permission.ts
4831
- var import_utils19 = require("@web-ts-toolkit/utils");
5156
+ var import_utils21 = require("@web-ts-toolkit/utils");
4832
5157
  var Permission = class {
4833
5158
  constructor(permissions) {
4834
5159
  this.permissions = { ...permissions };
@@ -4844,7 +5169,7 @@ var Permission = class {
4844
5169
  return [permissionOrPermissions].concat(permissions);
4845
5170
  }
4846
5171
  hasKey(permission) {
4847
- return (0, import_utils19.hasOwn)(this.permissions, permission);
5172
+ return (0, import_utils21.hasOwn)(this.permissions, permission);
4848
5173
  }
4849
5174
  has(permission) {
4850
5175
  return this.permissions[permission] === true;
@@ -4864,10 +5189,10 @@ var permission_default = Permission;
4864
5189
 
4865
5190
  // src/core-shared.ts
4866
5191
  function isAndFilter(filter2) {
4867
- return !!filter2 && (0, import_utils20.isPlainObject)(filter2) && Object.keys(filter2).length === 1 && (0, import_utils20.isArray)(filter2.$and);
5192
+ return !!filter2 && (0, import_utils22.isPlainObject)(filter2) && Object.keys(filter2).length === 1 && (0, import_utils22.isArray)(filter2.$and);
4868
5193
  }
4869
5194
  function isMergeableClause(filter2) {
4870
- return !!filter2 && (0, import_utils20.isPlainObject)(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
5195
+ return !!filter2 && (0, import_utils22.isPlainObject)(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
4871
5196
  }
4872
5197
  function optimizeAndFilter(clausesInput) {
4873
5198
  const clauses = [];
@@ -4882,10 +5207,10 @@ function optimizeAndFilter(clausesInput) {
4882
5207
  }
4883
5208
  }
4884
5209
  const dedupeKey = (value) => {
4885
- if ((0, import_utils20.isArray)(value)) {
5210
+ if ((0, import_utils22.isArray)(value)) {
4886
5211
  return `[${value.map(dedupeKey).join(",")}]`;
4887
5212
  }
4888
- if ((0, import_utils20.isPlainObject)(value)) {
5213
+ if ((0, import_utils22.isPlainObject)(value)) {
4889
5214
  return `{${Object.keys(value).sort().map((key) => `${key}:${dedupeKey(value[key])}`).join(",")}}`;
4890
5215
  }
4891
5216
  return JSON.stringify(value);
@@ -4909,7 +5234,7 @@ function optimizeAndFilter(clausesInput) {
4909
5234
  }
4910
5235
  let canMerge = true;
4911
5236
  for (const [key, value] of Object.entries(clause)) {
4912
- if ((0, import_utils20.hasOwn)(mergedClause, key) && !(0, import_utils20.isEqual)(mergedClause[key], value)) {
5237
+ if ((0, import_utils22.hasOwn)(mergedClause, key) && !(0, import_utils22.isEqual)(mergedClause[key], value)) {
4913
5238
  canMerge = false;
4914
5239
  break;
4915
5240
  }
@@ -4920,24 +5245,24 @@ function optimizeAndFilter(clausesInput) {
4920
5245
  }
4921
5246
  Object.assign(mergedClause, clause);
4922
5247
  }
4923
- const finalClauses = (0, import_utils20.isEmpty)(mergedClause) ? remainingClauses : [mergedClause, ...remainingClauses];
5248
+ const finalClauses = (0, import_utils22.isEmpty)(mergedClause) ? remainingClauses : [mergedClause, ...remainingClauses];
4924
5249
  if (finalClauses.length === 0) return null;
4925
5250
  if (finalClauses.length === 1) return finalClauses[0];
4926
5251
  return { $and: finalClauses };
4927
5252
  }
4928
5253
  function normalizeFilter(filter2) {
4929
5254
  if (filter2 === false) return false;
4930
- if (!filter2 || !(0, import_utils20.isPlainObject)(filter2) || (0, import_utils20.isEmpty)(filter2)) return null;
5255
+ if (!filter2 || !(0, import_utils22.isPlainObject)(filter2) || (0, import_utils22.isEmpty)(filter2)) return null;
4931
5256
  if (!isAndFilter(filter2)) {
4932
5257
  return filter2;
4933
5258
  }
4934
5259
  return optimizeAndFilter(filter2.$and);
4935
5260
  }
4936
5261
  async function resolveIdentifierFilter(req, idField, resolveIdFilter, id) {
4937
- if ((0, import_utils20.isFunction)(resolveIdFilter)) {
5262
+ if ((0, import_utils22.isFunction)(resolveIdFilter)) {
4938
5263
  return await resolveIdFilter.call(req, id);
4939
5264
  }
4940
- if ((0, import_utils20.isString)(idField)) {
5265
+ if ((0, import_utils22.isString)(idField)) {
4941
5266
  return { [idField]: id };
4942
5267
  }
4943
5268
  return { _id: id };
@@ -4953,12 +5278,12 @@ async function resolveAccessFilter({
4953
5278
  }) {
4954
5279
  let nextFilter = normalizeFilter(filter2);
4955
5280
  const overrideFilterFn = getOption(`overrideFilter.${access}`, null);
4956
- if (nextFilter !== false && (0, import_utils20.isFunction)(overrideFilterFn)) {
5281
+ if (nextFilter !== false && (0, import_utils22.isFunction)(overrideFilterFn)) {
4957
5282
  nextFilter = normalizeFilter(await overrideFilterFn.call(req, nextFilter, permissions));
4958
5283
  }
4959
5284
  if (nextFilter === false) return false;
4960
5285
  const baseFilterFn = getOption(`baseFilter.${access}`, null);
4961
- if (!(0, import_utils20.isFunction)(baseFilterFn)) return nextFilter ?? {};
5286
+ if (!(0, import_utils22.isFunction)(baseFilterFn)) return nextFilter ?? {};
4962
5287
  const baseFilter = normalizeFilter(
4963
5288
  cache.has(cacheKey) ? cache.get(cacheKey) : await baseFilterFn.call(req, permissions)
4964
5289
  );
@@ -4992,34 +5317,34 @@ async function setRequestPermissions(req) {
4992
5317
  if (owner === activeRuntime) return;
4993
5318
  if (hasFieldValue && owner === void 0) return;
4994
5319
  const globalPermissions = getGlobalOption("globalPermissions");
4995
- if (!(0, import_utils20.isFunction)(globalPermissions)) return;
5320
+ if (!(0, import_utils22.isFunction)(globalPermissions)) return;
4996
5321
  const permissions = await globalPermissions.call(req, req);
4997
- if ((0, import_utils20.isPlainObject)(permissions)) req[requestPermissionField] = permissions;
4998
- else if ((0, import_utils20.isArray)(permissions)) req[requestPermissionField] = (0, import_utils20.arrayToRecord)(permissions);
4999
- else if ((0, import_utils20.isString)(permissions)) req[requestPermissionField] = { [permissions]: true };
5322
+ if ((0, import_utils22.isPlainObject)(permissions)) req[requestPermissionField] = permissions;
5323
+ else if ((0, import_utils22.isArray)(permissions)) req[requestPermissionField] = (0, import_utils22.arrayToRecord)(permissions);
5324
+ else if ((0, import_utils22.isString)(permissions)) req[requestPermissionField] = { [permissions]: true };
5000
5325
  owners.set(requestPermissionField, activeRuntime);
5001
5326
  }
5002
5327
  async function evaluateRouteGuard(req, permissions, routeGuard) {
5003
5328
  const phas = (key) => permissions.has(key);
5004
5329
  const { stringHandler, arrayHandler } = createValidator(phas);
5005
- if ((0, import_utils20.isBoolean)(routeGuard)) {
5330
+ if ((0, import_utils22.isBoolean)(routeGuard)) {
5006
5331
  return routeGuard === true;
5007
5332
  }
5008
- if ((0, import_utils20.isString)(routeGuard)) {
5333
+ if ((0, import_utils22.isString)(routeGuard)) {
5009
5334
  return stringHandler(routeGuard);
5010
5335
  }
5011
- if ((0, import_utils20.isArray)(routeGuard)) {
5336
+ if ((0, import_utils22.isArray)(routeGuard)) {
5012
5337
  return arrayHandler(routeGuard);
5013
5338
  }
5014
- if ((0, import_utils20.isFunction)(routeGuard)) {
5339
+ if ((0, import_utils22.isFunction)(routeGuard)) {
5015
5340
  return routeGuard.call(req, permissions);
5016
5341
  }
5017
5342
  return false;
5018
5343
  }
5019
5344
  async function callHookChain(req, hook, doc, permissions, context) {
5020
- const hooks = (0, import_utils20.castArray)(hook);
5345
+ const hooks = (0, import_utils22.castArray)(hook);
5021
5346
  for (let x = 0; x < hooks.length; x++) {
5022
- if ((0, import_utils20.isFunction)(hooks[x])) {
5347
+ if ((0, import_utils22.isFunction)(hooks[x])) {
5023
5348
  doc = await hooks[x].call(req, doc, permissions, context);
5024
5349
  }
5025
5350
  }
@@ -5042,13 +5367,13 @@ async function collectSchemaFields({
5042
5367
  if (baseFields.includes(key)) continue;
5043
5368
  const val = permissionSchema[key];
5044
5369
  const value = val && val[access] || val;
5045
- if ((0, import_utils20.isBoolean)(value)) {
5370
+ if ((0, import_utils22.isBoolean)(value)) {
5046
5371
  if (value) fields.push(key);
5047
- } else if ((0, import_utils20.isString)(value)) {
5372
+ } else if ((0, import_utils22.isString)(value)) {
5048
5373
  if (stringHandler(value)) fields.push(key);
5049
- } else if ((0, import_utils20.isArray)(value)) {
5374
+ } else if ((0, import_utils22.isArray)(value)) {
5050
5375
  if (arrayHandler(value)) fields.push(key);
5051
- } else if ((0, import_utils20.isFunction)(value)) {
5376
+ } else if ((0, import_utils22.isFunction)(value)) {
5052
5377
  if (await value.call(req, ...functionArgs)) fields.push(key);
5053
5378
  }
5054
5379
  }
@@ -5134,7 +5459,7 @@ async function runDecorateAllHook({
5134
5459
  }
5135
5460
 
5136
5461
  // src/acl/select-resolution.ts
5137
- var import_utils21 = require("@web-ts-toolkit/utils");
5462
+ var import_utils23 = require("@web-ts-toolkit/utils");
5138
5463
  async function collectAllowedFieldsForRequest({
5139
5464
  req,
5140
5465
  permissionSchema,
@@ -5202,18 +5527,19 @@ async function resolveSelectForRequest({
5202
5527
  const excludeall = normalizedSelect.every((v) => v.startsWith("-"));
5203
5528
  if (excludeall) {
5204
5529
  normalizedSelect = normalizedSelect.map((v) => v.substring(1));
5205
- fields = (0, import_utils21.difference)(fields, normalizedSelect);
5530
+ fields = (0, import_utils23.difference)(fields, normalizedSelect);
5206
5531
  if (excludeid) fields.push("-_id");
5207
5532
  } else {
5208
- fields = (0, import_utils21.intersection)(normalizedSelect, fields.concat(excludeid ? "-_id" : "_id"));
5533
+ fields = (0, import_utils23.intersection)(normalizedSelect, fields.concat(excludeid ? "-_id" : "_id"));
5209
5534
  }
5210
5535
  }
5211
5536
  return fields.concat(alwaysSelectFields);
5212
5537
  }
5213
- return (0, import_utils21.intersection)(normalizedSelect, fields);
5538
+ return (0, import_utils23.intersection)(normalizedSelect, fields);
5214
5539
  }
5215
5540
 
5216
5541
  // src/core.ts
5542
+ var emptyPermissionLeaf = () => ({ $: "_" });
5217
5543
  var Core = class {
5218
5544
  constructor(req) {
5219
5545
  this.cachedPermissions = null;
@@ -5225,10 +5551,10 @@ var Core = class {
5225
5551
  getIdentifier(modelName) {
5226
5552
  const resolveIdFilter = getModelOption(modelName, "resolveIdFilter");
5227
5553
  const idField = getModelOption(modelName, "idField");
5228
- if ((0, import_utils22.isFunction)(resolveIdFilter)) {
5554
+ if ((0, import_utils24.isFunction)(resolveIdFilter)) {
5229
5555
  return null;
5230
5556
  }
5231
- if ((0, import_utils22.isString)(idField)) {
5557
+ if ((0, import_utils24.isString)(idField)) {
5232
5558
  return idField;
5233
5559
  }
5234
5560
  return "_id";
@@ -5306,11 +5632,11 @@ var Core = class {
5306
5632
  async genPopulate(modelName, access = "read", _populate = null, subPaths = []) {
5307
5633
  if (!_populate) return [];
5308
5634
  let populate = Array.isArray(_populate) ? _populate : [_populate];
5309
- populate = (0, import_utils22.compact)(
5635
+ populate = (0, import_utils24.compact)(
5310
5636
  await Promise.all(
5311
5637
  populate.map(async (p) => {
5312
- const populateAccess = !(0, import_utils22.isString)(p) && p.access ? p.access : access;
5313
- const ret = (0, import_utils22.isString)(p) ? { path: p } : {
5638
+ const populateAccess = !(0, import_utils24.isString)(p) && p.access ? p.access : access;
5639
+ const ret = (0, import_utils24.isString)(p) ? { path: p } : {
5314
5640
  path: p.path,
5315
5641
  select: normalizeSelect(p.select)
5316
5642
  };
@@ -5348,10 +5674,10 @@ var Core = class {
5348
5674
  }
5349
5675
  async validate(modelName, allowedData, access, context) {
5350
5676
  const validate = getModelOption(modelName, `validate.${access}`, null);
5351
- if ((0, import_utils22.isFunction)(validate)) {
5677
+ if ((0, import_utils24.isFunction)(validate)) {
5352
5678
  const permissions = this.getGlobalPermissions();
5353
5679
  return validate.call(this.req, allowedData, permissions, context);
5354
- } else if ((0, import_utils22.isBoolean)(validate) || (0, import_utils22.isArray)(validate)) {
5680
+ } else if ((0, import_utils24.isBoolean)(validate) || (0, import_utils24.isArray)(validate)) {
5355
5681
  return validate;
5356
5682
  } else {
5357
5683
  return true;
@@ -5376,7 +5702,7 @@ var Core = class {
5376
5702
  const changeOptions = getModelOption(modelName, `onChange`, {});
5377
5703
  for (let x = 0; x < context.modifiedPaths.length; x++) {
5378
5704
  const mpath = context.modifiedPaths[x];
5379
- if ((0, import_utils22.isFunction)(changeOptions[mpath])) {
5705
+ if ((0, import_utils24.isFunction)(changeOptions[mpath])) {
5380
5706
  await changeOptions[mpath].call(
5381
5707
  this.req,
5382
5708
  context.originalDocumentSnapshot[mpath],
@@ -5400,7 +5726,7 @@ var Core = class {
5400
5726
  async genDocPermissions(modelName, doc, access, context) {
5401
5727
  const docPermissionsFn = getModelOption(modelName, `docPermissions.${access}`, null);
5402
5728
  let docPermissions = {};
5403
- if ((0, import_utils22.isFunction)(docPermissionsFn)) {
5729
+ if ((0, import_utils24.isFunction)(docPermissionsFn)) {
5404
5730
  const permissions = this.getGlobalPermissions();
5405
5731
  try {
5406
5732
  docPermissions = await docPermissionsFn.call(this.req, doc, permissions, context);
@@ -5417,7 +5743,7 @@ var Core = class {
5417
5743
  }
5418
5744
  addEmptyPermissions(modelName, doc) {
5419
5745
  const docPermissionField = getModelOption(modelName, "documentPermissionField");
5420
- setDocValue(doc, docPermissionField, { _view: { $: "_" }, _edit: { $: "_" } });
5746
+ setDocValue(doc, docPermissionField, { _view: emptyPermissionLeaf(), _edit: emptyPermissionLeaf() });
5421
5747
  return doc;
5422
5748
  }
5423
5749
  async addDocPermissions(modelName, doc, access, context) {
@@ -5451,7 +5777,7 @@ var Core = class {
5451
5777
  readExists ? this.genAllowedFields(modelName, doc, "read") : [],
5452
5778
  updateExists ? this.genAllowedFields(modelName, doc, "update") : []
5453
5779
  ]);
5454
- const viewObj = (0, import_utils22.reduce)(
5780
+ const viewObj = (0, import_utils24.reduce)(
5455
5781
  views,
5456
5782
  (ret, view) => {
5457
5783
  ret[view] = true;
@@ -5459,7 +5785,7 @@ var Core = class {
5459
5785
  },
5460
5786
  {}
5461
5787
  );
5462
- const editObj = (0, import_utils22.reduce)(
5788
+ const editObj = (0, import_utils24.reduce)(
5463
5789
  edits,
5464
5790
  (ret, view) => {
5465
5791
  ret[view] = true;
@@ -5467,8 +5793,8 @@ var Core = class {
5467
5793
  },
5468
5794
  {}
5469
5795
  );
5470
- setDocValue(doc, `${docPermissionField}._view`, viewObj);
5471
- setDocValue(doc, `${docPermissionField}._edit`, editObj);
5796
+ setDocValue(doc, `${docPermissionField}._view`, Object.keys(viewObj).length > 0 ? viewObj : emptyPermissionLeaf());
5797
+ setDocValue(doc, `${docPermissionField}._edit`, Object.keys(editObj).length > 0 ? editObj : emptyPermissionLeaf());
5472
5798
  return doc;
5473
5799
  }
5474
5800
  async decorate(modelName, doc, access, context) {
@@ -5483,9 +5809,9 @@ var Core = class {
5483
5809
  return runDecorateAllHook({ req: this.req, hook: decorateAll, docs, permissions, context });
5484
5810
  }
5485
5811
  runTasks(modelName, docObject, task) {
5486
- const tasks = (0, import_utils22.compact)((0, import_utils22.castArray)(task));
5812
+ const tasks = (0, import_utils24.compact)((0, import_utils24.castArray)(task));
5487
5813
  if (tasks.length === 0) return docObject;
5488
- (0, import_utils22.forEach)(tasks, (task2) => {
5814
+ (0, import_utils24.forEach)(tasks, (task2) => {
5489
5815
  const { type, args, options } = task2;
5490
5816
  switch (type) {
5491
5817
  case "COPY_AND_DEPOPULATE":
@@ -5553,9 +5879,9 @@ var Core = class {
5553
5879
  }
5554
5880
  const [, field, op] = keys2;
5555
5881
  const subOption = getExactModelOption(modelName, `operationAccess.${access}`);
5556
- if ((0, import_utils22.isUndefined)(subOption)) {
5882
+ if ((0, import_utils24.isUndefined)(subOption)) {
5557
5883
  const subFieldOption = getExactModelOption(modelName, `operationAccess.subs.${field}`);
5558
- if ((0, import_utils22.isUndefined)(subFieldOption)) {
5884
+ if ((0, import_utils24.isUndefined)(subFieldOption)) {
5559
5885
  const opOption = getModelOption(modelName, `operationAccess.${op}`);
5560
5886
  return this.canActivate(opOption);
5561
5887
  }
@@ -5607,25 +5933,25 @@ function guard(condition) {
5607
5933
  const permissions = req[PERMISSIONS];
5608
5934
  let cond = condition;
5609
5935
  let phas = (key) => permissions.has(key);
5610
- if ((0, import_utils23.isPlainObject)(condition)) {
5936
+ if ((0, import_utils25.isPlainObject)(condition)) {
5611
5937
  const { modelName, id, condition: _cond } = condition;
5612
5938
  const svc = req.macl.getPublicService(modelName);
5613
5939
  const select = getModelOption(modelName, `alwaysSelectFields.read`, void 0);
5614
- let _id = (0, import_utils23.isString)(id) ? id : null;
5615
- if ((0, import_utils23.isPlainObject)(id)) {
5940
+ let _id = (0, import_utils25.isString)(id) ? id : null;
5941
+ if ((0, import_utils25.isPlainObject)(id)) {
5616
5942
  const { type = "param", key } = id;
5617
5943
  if (type === "param") {
5618
5944
  const paramValue = req.params[key];
5619
- _id = (0, import_utils23.isArray)(paramValue) ? paramValue[0] ?? null : paramValue ?? null;
5945
+ _id = (0, import_utils25.isArray)(paramValue) ? paramValue[0] ?? null : paramValue ?? null;
5620
5946
  } else if (type === "query") {
5621
5947
  const _qval = req.query[key];
5622
- if ((0, import_utils23.isArray)(_qval)) _id = _qval.length > 0 ? _qval[0] : null;
5948
+ if ((0, import_utils25.isArray)(_qval)) _id = _qval.length > 0 ? _qval[0] : null;
5623
5949
  else _id = _qval;
5624
5950
  } else {
5625
5951
  _id = null;
5626
5952
  }
5627
5953
  }
5628
- if (!(0, import_utils23.isString)(_id) || !_id) {
5954
+ if (!(0, import_utils25.isString)(_id) || !_id) {
5629
5955
  return next(new import_express_json_router2.default.clientErrors.BadRequestError());
5630
5956
  }
5631
5957
  const result = await svc._read(_id, { select });
@@ -5637,11 +5963,11 @@ function guard(condition) {
5637
5963
  cond = _cond;
5638
5964
  }
5639
5965
  const { stringHandler, arrayHandler } = createValidator(phas);
5640
- if ((0, import_utils23.isString)(cond)) {
5966
+ if ((0, import_utils25.isString)(cond)) {
5641
5967
  if (stringHandler(cond)) return next();
5642
- } else if ((0, import_utils23.isArray)(cond)) {
5968
+ } else if ((0, import_utils25.isArray)(cond)) {
5643
5969
  if (arrayHandler(cond)) return next();
5644
- } else if ((0, import_utils23.isFunction)(cond)) {
5970
+ } else if ((0, import_utils25.isFunction)(cond)) {
5645
5971
  const result = await cond.call(req, permissions);
5646
5972
  if (!!result) return next();
5647
5973
  }
@@ -5654,7 +5980,7 @@ var import_express2 = __toESM(require("express"));
5654
5980
 
5655
5981
  // src/routers/model-router.ts
5656
5982
  var import_express_json_router5 = __toESM(require("@web-ts-toolkit/express-json-router"));
5657
- var import_utils27 = require("@web-ts-toolkit/utils");
5983
+ var import_utils29 = require("@web-ts-toolkit/utils");
5658
5984
 
5659
5985
  // src/routers/router-mutation.ts
5660
5986
  var MODEL_BUILD_TIME_OPTION_KEYS = /* @__PURE__ */ new Set([
@@ -5783,7 +6109,7 @@ var import_express_json_router4 = __toESM(require("@web-ts-toolkit/express-json-
5783
6109
 
5784
6110
  // src/validation/common.ts
5785
6111
  var import_zod2 = require("zod");
5786
- var import_utils24 = require("@web-ts-toolkit/utils");
6112
+ var import_utils26 = require("@web-ts-toolkit/utils");
5787
6113
  var stringOrStringArray = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.array(import_zod2.z.string())]);
5788
6114
  var safeIntegerString = (min, message) => import_zod2.z.string().regex(/^\d+$/, message).refine((value) => Number.isSafeInteger(Number(value)), "Expected a safe integer").refine((value) => Number(value) >= min, message);
5789
6115
  var queryBooleanString = import_zod2.z.enum(["true", "false"]);
@@ -5883,7 +6209,7 @@ var correlatedFilterSchema = import_zod2.z.record(import_zod2.z.string(), import
5883
6209
  }
5884
6210
  });
5885
6211
  var correlatedEmptyOptionsSchema = import_zod2.z.unknown().refine(
5886
- (value) => value === void 0 || (0, import_utils24.isPlainObject)(value) && Object.keys(value).length === 0,
6212
+ (value) => value === void 0 || (0, import_utils26.isPlainObject)(value) && Object.keys(value).length === 0,
5887
6213
  "Correlated include options must be absent or empty"
5888
6214
  ).optional();
5889
6215
  var correlatedPathSchema = import_zod2.z.string().min(1).superRefine((path, ctx) => {
@@ -5920,7 +6246,7 @@ var correlatedListArgsSchema = import_zod2.z.object({
5920
6246
  include: import_zod2.z.lazy(() => correlatedNestedIncludeSchema).optional()
5921
6247
  }).strict().superRefine((args, ctx) => checkArgsMarkers(args.select, args.sort, ctx));
5922
6248
  var correlatedCountArgsSchema = import_zod2.z.unknown().refine(
5923
- (value) => value === void 0 || (0, import_utils24.isPlainObject)(value) && Object.keys(value).length === 0,
6249
+ (value) => value === void 0 || (0, import_utils26.isPlainObject)(value) && Object.keys(value).length === 0,
5924
6250
  "Count includes accept no args"
5925
6251
  ).optional();
5926
6252
  var correlatedReadByIdSchema = import_zod2.z.object({
@@ -5970,7 +6296,7 @@ var correlatedNestedIncludeSchema = import_zod2.z.union([includeItemSchema, impo
5970
6296
  var rejectDuplicateCorrelatedPaths = (items, ctx) => {
5971
6297
  const seen = /* @__PURE__ */ new Set();
5972
6298
  items.forEach((item, index) => {
5973
- if ((0, import_utils24.isPlainObject)(item) && item.mode === "correlated" && typeof item.path === "string") {
6299
+ if ((0, import_utils26.isPlainObject)(item) && item.mode === "correlated" && typeof item.path === "string") {
5974
6300
  if (seen.has(item.path)) {
5975
6301
  ctx.addIssue({
5976
6302
  code: import_zod2.z.ZodIssueCode.custom,
@@ -6169,19 +6495,12 @@ function fromJoi(schema) {
6169
6495
  }
6170
6496
  function fromAjv(validator) {
6171
6497
  return async (value) => {
6172
- const outcome = validator(value);
6173
- if (isThenable(outcome)) {
6498
+ if (validator.$async === true) {
6174
6499
  try {
6175
- const data = await outcome;
6176
- if (data === false) {
6177
- return {
6178
- success: false,
6179
- issues: normalizeAjvIssues(validator.errors ?? [])
6180
- };
6181
- }
6500
+ const data = await validator(value);
6182
6501
  return {
6183
6502
  success: true,
6184
- data: data === true ? value : data
6503
+ data
6185
6504
  };
6186
6505
  } catch (error2) {
6187
6506
  if (!isAjvValidationError(error2)) {
@@ -6189,11 +6508,21 @@ function fromAjv(validator) {
6189
6508
  }
6190
6509
  return {
6191
6510
  success: false,
6192
- issues: normalizeAjvIssues(error2.errors ?? [])
6511
+ issues: normalizeAjvIssues(error2.errors)
6193
6512
  };
6194
6513
  }
6195
6514
  }
6196
- const errorsSnapshot = validator.errors ? [...validator.errors] : [];
6515
+ const outcome = validator(value);
6516
+ if (isThenable(outcome)) {
6517
+ void Promise.resolve(outcome).catch(() => {
6518
+ });
6519
+ throw new TypeError(
6520
+ "fromAjv: promise/thenable validators must declare $async: true and resolve with validated data"
6521
+ );
6522
+ }
6523
+ if (typeof outcome !== "boolean") {
6524
+ throw new TypeError("fromAjv: synchronous validators must return a boolean verdict");
6525
+ }
6197
6526
  if (outcome) {
6198
6527
  return {
6199
6528
  success: true,
@@ -6202,7 +6531,9 @@ function fromAjv(validator) {
6202
6531
  }
6203
6532
  return {
6204
6533
  success: false,
6205
- issues: normalizeAjvIssues(errorsSnapshot)
6534
+ // Normalize immediately, copying message/path before another call can
6535
+ // replace the errors array or mutate its entries.
6536
+ issues: normalizeAjvIssues(validator.errors ?? [])
6206
6537
  };
6207
6538
  };
6208
6539
  }
@@ -6432,14 +6763,14 @@ function isArkTypeErrors(value) {
6432
6763
  return record.arkKind === "errors" || record[" arkKind"] === "errors";
6433
6764
  }
6434
6765
  function isThenable(value) {
6435
- return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
6766
+ return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
6436
6767
  }
6437
6768
  function isAjvValidationError(error2) {
6438
6769
  if (typeof error2 !== "object" || error2 === null || !("errors" in error2)) {
6439
6770
  return false;
6440
6771
  }
6441
- const errors = error2.errors;
6442
- return errors == null || Array.isArray(errors);
6772
+ const candidate = error2;
6773
+ return candidate.ajv === true && candidate.validation === true && Array.isArray(candidate.errors);
6443
6774
  }
6444
6775
  function isVineValidationError(error2) {
6445
6776
  if (typeof error2 !== "object" || error2 === null || !("messages" in error2)) {
@@ -6878,7 +7209,7 @@ var rootDataQueryEntrySchema = import_zod5.z.union([
6878
7209
  var rootQuerySchema = import_zod5.z.array(import_zod5.z.union([rootModelQueryEntrySchema, rootDataQueryEntrySchema]));
6879
7210
 
6880
7211
  // src/routers/shared.ts
6881
- var import_utils25 = require("@web-ts-toolkit/utils");
7212
+ var import_utils27 = require("@web-ts-toolkit/utils");
6882
7213
 
6883
7214
  // src/routers/model-router-collection-routes.ts
6884
7215
  function setModelCollectionRoutes(context) {
@@ -6896,14 +7227,14 @@ function setModelCollectionRoutes(context) {
6896
7227
  await context.assertAllowed(req, "list");
6897
7228
  const { skip, limit, page, page_size, skim, include_permissions, include_count, include_extra_headers } = parseQuery(requestSchemas.listQuery, req.query);
6898
7229
  const svc = context.getPublicService(req);
6899
- const includeCount = (0, import_utils25.parseBooleanString)(include_count);
6900
- const includeExtraHeaders = (0, import_utils25.parseBooleanString)(include_extra_headers);
7230
+ const includeCount = (0, import_utils27.parseBooleanString)(include_count);
7231
+ const includeExtraHeaders = (0, import_utils27.parseBooleanString)(include_extra_headers);
6901
7232
  const result = await svc._list(
6902
7233
  {},
6903
7234
  { skip, limit, page, pageSize: page_size },
6904
7235
  {
6905
- skim: (0, import_utils25.parseBooleanString)(skim),
6906
- includePermissions: (0, import_utils25.parseBooleanString)(include_permissions),
7236
+ skim: (0, import_utils27.parseBooleanString)(skim),
7237
+ includePermissions: (0, import_utils27.parseBooleanString)(include_permissions),
6907
7238
  includeCount
6908
7239
  }
6909
7240
  );
@@ -6958,7 +7289,7 @@ function setModelCollectionRoutes(context) {
6958
7289
  context.getRequestSchema("requestSchemas.create")
6959
7290
  );
6960
7291
  const svc = context.getPublicService(req);
6961
- const result = await svc._create(data, {}, { includePermissions: (0, import_utils25.parseBooleanString)(include_permissions) });
7292
+ const result = await svc._create(data, {}, { includePermissions: (0, import_utils27.parseBooleanString)(include_permissions) });
6962
7293
  handleResultError(result);
6963
7294
  return formatModelCreatedResponse(result);
6964
7295
  });
@@ -6994,7 +7325,7 @@ function setModelCollectionRoutes(context) {
6994
7325
  finalData,
6995
7326
  { select: finalSelect, populate: finalPopulate, tasks: finalTasks },
6996
7327
  {
6997
- includePermissions: includePermissions ?? (0, import_utils25.parseBooleanString)(include_permissions),
7328
+ includePermissions: includePermissions ?? (0, import_utils27.parseBooleanString)(include_permissions),
6998
7329
  populateAccess
6999
7330
  }
7000
7331
  );
@@ -7086,8 +7417,8 @@ function setModelDocumentRoutes(context) {
7086
7417
  id,
7087
7418
  {},
7088
7419
  {
7089
- includePermissions: (0, import_utils25.parseBooleanString)(include_permissions),
7090
- tryList: (0, import_utils25.parseBooleanString)(try_list)
7420
+ includePermissions: (0, import_utils27.parseBooleanString)(include_permissions),
7421
+ tryList: (0, import_utils27.parseBooleanString)(try_list)
7091
7422
  }
7092
7423
  );
7093
7424
  handleResultError(result);
@@ -7172,8 +7503,8 @@ function setModelDocumentRoutes(context) {
7172
7503
  data,
7173
7504
  {},
7174
7505
  {
7175
- returningAll: (0, import_utils25.parseBooleanString)(returning_all),
7176
- includePermissions: (0, import_utils25.parseBooleanString)(include_permissions)
7506
+ returningAll: (0, import_utils27.parseBooleanString)(returning_all),
7507
+ includePermissions: (0, import_utils27.parseBooleanString)(include_permissions)
7177
7508
  }
7178
7509
  );
7179
7510
  handleResultError(result);
@@ -7219,8 +7550,8 @@ function setModelDocumentRoutes(context) {
7219
7550
  finalData,
7220
7551
  { select: finalSelect, populate: finalPopulate, tasks: finalTasks },
7221
7552
  {
7222
- returningAll: returningAll ?? (0, import_utils25.parseBooleanString)(returning_all),
7223
- includePermissions: includePermissions ?? (0, import_utils25.parseBooleanString)(include_permissions),
7553
+ returningAll: returningAll ?? (0, import_utils27.parseBooleanString)(returning_all),
7554
+ includePermissions: includePermissions ?? (0, import_utils27.parseBooleanString)(include_permissions),
7224
7555
  populateAccess
7225
7556
  }
7226
7557
  );
@@ -7248,8 +7579,8 @@ function setModelDocumentRoutes(context) {
7248
7579
  body,
7249
7580
  {},
7250
7581
  {
7251
- returningAll: (0, import_utils25.parseBooleanString)(returning_all),
7252
- includePermissions: (0, import_utils25.parseBooleanString)(include_permissions)
7582
+ returningAll: (0, import_utils27.parseBooleanString)(returning_all),
7583
+ includePermissions: (0, import_utils27.parseBooleanString)(include_permissions)
7253
7584
  }
7254
7585
  );
7255
7586
  handleResultError(result);
@@ -7293,8 +7624,8 @@ function setModelDocumentRoutes(context) {
7293
7624
  finalData,
7294
7625
  { select: finalSelect, populate: finalPopulate, tasks: finalTasks },
7295
7626
  {
7296
- returningAll: returningAll ?? (0, import_utils25.parseBooleanString)(returning_all),
7297
- includePermissions: includePermissions ?? (0, import_utils25.parseBooleanString)(include_permissions),
7627
+ returningAll: returningAll ?? (0, import_utils27.parseBooleanString)(returning_all),
7628
+ includePermissions: includePermissions ?? (0, import_utils27.parseBooleanString)(include_permissions),
7298
7629
  populateAccess
7299
7630
  }
7300
7631
  );
@@ -7532,11 +7863,11 @@ function setModelSubDocumentRoutes(context) {
7532
7863
  }
7533
7864
 
7534
7865
  // src/openapi/route-registration.ts
7535
- var import_utils26 = require("@web-ts-toolkit/utils");
7866
+ var import_utils28 = require("@web-ts-toolkit/utils");
7536
7867
  function registerOpenApiRoute(runtime, basePath, defaultTag, route) {
7537
7868
  runtime.registerOpenApiRoute({
7538
7869
  ...route,
7539
- path: (0, import_utils26.normalizeUrlPath)(basePath + route.path),
7870
+ path: (0, import_utils28.normalizeUrlPath)(basePath + route.path),
7540
7871
  tags: route.tags ?? [defaultTag]
7541
7872
  });
7542
7873
  }
@@ -7544,8 +7875,8 @@ function registerOpenApiRoute(runtime, basePath, defaultTag, route) {
7544
7875
  // src/routers/model-router.ts
7545
7876
  var clientErrors2 = import_express_json_router5.default.clientErrors;
7546
7877
  function setOption(parentKey, optionKey, option) {
7547
- const key = (0, import_utils27.isUndefined)(option) ? parentKey : `${parentKey}.${optionKey}`;
7548
- const value = (0, import_utils27.isUndefined)(option) ? optionKey : option;
7878
+ const key = (0, import_utils29.isUndefined)(option) ? parentKey : `${parentKey}.${optionKey}`;
7879
+ const value = (0, import_utils29.isUndefined)(option) ? optionKey : option;
7549
7880
  assertMutableRouterOption("model", key);
7550
7881
  this.runtime.setModelOption(this.modelName, key, value);
7551
7882
  return this;
@@ -7707,7 +8038,7 @@ var ModelRouter = class _ModelRouter {
7707
8038
  this.runtime = runtime;
7708
8039
  this.runtime.setModelOptions(modelName, initialOptions);
7709
8040
  this.options = this.runtime.getModelOptions(modelName);
7710
- this.fullBasePath = (0, import_utils27.normalizeUrlPath)(this.options.parentPath + this.options.basePath);
8041
+ this.fullBasePath = (0, import_utils29.normalizeUrlPath)(this.options.parentPath + this.options.basePath);
7711
8042
  this.modelName = modelName;
7712
8043
  this.router = new import_express_json_router5.default(this.options.basePath, createSetCore(this.runtime), accessRouterResponseHandler);
7713
8044
  this.model = new model_default(modelName, this.runtime);
@@ -7787,13 +8118,13 @@ var ModelRouter = class _ModelRouter {
7787
8118
  }
7788
8119
  logEndpoints() {
7789
8120
  runWithRuntime(this.runtime, () => {
7790
- (0, import_utils27.forEach)(this.router.getEndpoints(), ({ method, path }) => {
7791
- logInfoMessage(`${(0, import_utils27.padEnd)(method, 6)} ${(0, import_utils27.normalizeUrlPath)(this.options.parentPath + path)}`);
8121
+ (0, import_utils29.forEach)(this.router.getEndpoints(), ({ method, path }) => {
8122
+ logInfoMessage(`${(0, import_utils29.padEnd)(method, 6)} ${(0, import_utils29.normalizeUrlPath)(this.options.parentPath + path)}`);
7792
8123
  });
7793
8124
  });
7794
8125
  }
7795
8126
  set(keyOrOptions, value) {
7796
- if (arguments.length === 2 && (0, import_utils27.isString)(keyOrOptions)) {
8127
+ if (arguments.length === 2 && (0, import_utils29.isString)(keyOrOptions)) {
7797
8128
  assertMutableRouterOption("model", keyOrOptions);
7798
8129
  this.runtime.setModelOption(
7799
8130
  this.modelName,
@@ -7801,7 +8132,7 @@ var ModelRouter = class _ModelRouter {
7801
8132
  value
7802
8133
  );
7803
8134
  }
7804
- if (arguments.length === 1 && (0, import_utils27.isPlainObject)(keyOrOptions)) {
8135
+ if (arguments.length === 1 && (0, import_utils29.isPlainObject)(keyOrOptions)) {
7805
8136
  assertMutableRouterOptions("model", keyOrOptions);
7806
8137
  this.runtime.setModelOptions(this.modelName, keyOrOptions);
7807
8138
  }
@@ -7824,7 +8155,7 @@ var ModelRouter = class _ModelRouter {
7824
8155
 
7825
8156
  // src/routers/root-router.ts
7826
8157
  var import_express_json_router6 = __toESM(require("@web-ts-toolkit/express-json-router"));
7827
- var import_utils28 = require("@web-ts-toolkit/utils");
8158
+ var import_utils30 = require("@web-ts-toolkit/utils");
7828
8159
 
7829
8160
  // src/core-data.ts
7830
8161
  var DataCore = class {
@@ -8090,7 +8421,7 @@ var RootRouter = class {
8090
8421
  this.setRoutes();
8091
8422
  }
8092
8423
  registerOpenApiRoute(route) {
8093
- registerOpenApiRoute(this.runtime, (0, import_utils28.normalizeUrlPath)(this.basename), "root", route);
8424
+ registerOpenApiRoute(this.runtime, (0, import_utils30.normalizeUrlPath)(this.basename), "root", route);
8094
8425
  }
8095
8426
  wrapResult(index, item, result) {
8096
8427
  return {
@@ -8151,7 +8482,7 @@ var RootRouter = class {
8151
8482
  groupItemsByOrder(items) {
8152
8483
  const groups = /* @__PURE__ */ new Map();
8153
8484
  items.forEach((item, index) => {
8154
- const order = (0, import_utils28.isNumber)(item.order) ? item.order : 0;
8485
+ const order = (0, import_utils30.isNumber)(item.order) ? item.order : 0;
8155
8486
  const group = groups.get(order);
8156
8487
  if (group) {
8157
8488
  group.push({ index, item });
@@ -8222,7 +8553,7 @@ var RootRouter = class {
8222
8553
  const arrResult = await this.runGroup(req, groupedItems[x]);
8223
8554
  results.push(...arrResult);
8224
8555
  }
8225
- return (0, import_utils28.orderBy)(results, ["index"], ["asc"]);
8556
+ return (0, import_utils30.orderBy)(results, ["index"], ["asc"]);
8226
8557
  });
8227
8558
  this.registerOpenApiRoute({
8228
8559
  method: "post",
@@ -8240,11 +8571,11 @@ var RootRouter = class {
8240
8571
 
8241
8572
  // src/routers/data-router.ts
8242
8573
  var import_express_json_router7 = __toESM(require("@web-ts-toolkit/express-json-router"));
8243
- var import_utils29 = require("@web-ts-toolkit/utils");
8574
+ var import_utils31 = require("@web-ts-toolkit/utils");
8244
8575
  var clientErrors4 = import_express_json_router7.default.clientErrors;
8245
8576
  function setOption2(parentKey, optionKey, option) {
8246
- const key = (0, import_utils29.isUndefined)(option) ? parentKey : `${parentKey}.${optionKey}`;
8247
- const value = (0, import_utils29.isUndefined)(option) ? optionKey : option;
8577
+ const key = (0, import_utils31.isUndefined)(option) ? parentKey : `${parentKey}.${optionKey}`;
8578
+ const value = (0, import_utils31.isUndefined)(option) ? optionKey : option;
8248
8579
  assertMutableRouterOption("data", key);
8249
8580
  this.runtime.setDataOption(this.dataName, key, value);
8250
8581
  return this;
@@ -8285,7 +8616,7 @@ var DataRouter = class {
8285
8616
  this.runtime = runtime;
8286
8617
  this.runtime.setDataOptions(dataName, initialOptions);
8287
8618
  this.options = this.runtime.getDataOptions(dataName);
8288
- this.fullBasePath = (0, import_utils29.normalizeUrlPath)(this.options.parentPath + this.options.basePath);
8619
+ this.fullBasePath = (0, import_utils31.normalizeUrlPath)(this.options.parentPath + this.options.basePath);
8289
8620
  this.dataName = dataName;
8290
8621
  this.router = new import_express_json_router7.default(this.options.basePath, createSetDataCore(this.runtime), accessRouterResponseHandler);
8291
8622
  this.setCollectionRoutes();
@@ -8318,8 +8649,8 @@ var DataRouter = class {
8318
8649
  req.query
8319
8650
  );
8320
8651
  const svc = this.getService(req);
8321
- const includeCount = (0, import_utils25.parseBooleanString)(include_count);
8322
- const includeExtraHeaders = (0, import_utils25.parseBooleanString)(include_extra_headers);
8652
+ const includeCount = (0, import_utils27.parseBooleanString)(include_count);
8653
+ const includeExtraHeaders = (0, import_utils27.parseBooleanString)(include_extra_headers);
8323
8654
  const result = await svc.find(
8324
8655
  {},
8325
8656
  { skip, limit, page, pageSize: page_size },
@@ -8437,11 +8768,11 @@ var DataRouter = class {
8437
8768
  });
8438
8769
  }
8439
8770
  set(keyOrOptions, value) {
8440
- if (arguments.length === 2 && (0, import_utils29.isString)(keyOrOptions)) {
8771
+ if (arguments.length === 2 && (0, import_utils31.isString)(keyOrOptions)) {
8441
8772
  assertMutableRouterOption("data", keyOrOptions);
8442
8773
  this.runtime.setDataOption(this.dataName, keyOrOptions, value);
8443
8774
  }
8444
- if (arguments.length === 1 && (0, import_utils29.isPlainObject)(keyOrOptions)) {
8775
+ if (arguments.length === 1 && (0, import_utils31.isPlainObject)(keyOrOptions)) {
8445
8776
  assertMutableRouterOptions("data", keyOrOptions);
8446
8777
  this.runtime.setDataOptions(this.dataName, keyOrOptions);
8447
8778
  }
@@ -8564,7 +8895,7 @@ function createRuntimeApi(runtime) {
8564
8895
  const wtt2 = runtimeMiddleware;
8565
8896
  wtt2.runtime = runtime;
8566
8897
  wtt2.createRouter = function(modelName, options) {
8567
- if ((0, import_utils30.isUndefined)(options)) {
8898
+ if ((0, import_utils32.isUndefined)(options)) {
8568
8899
  return new RootRouter(modelName, runtime);
8569
8900
  }
8570
8901
  if (typeof modelName === "string") {
@@ -8585,10 +8916,10 @@ function createRuntimeApi(runtime) {
8585
8916
  };
8586
8917
  wtt2.combineRoutes = combineRoutes;
8587
8918
  wtt2.set = function(keyOrOptions, value) {
8588
- if (arguments.length === 2 && (0, import_utils30.isString)(keyOrOptions)) {
8919
+ if (arguments.length === 2 && (0, import_utils32.isString)(keyOrOptions)) {
8589
8920
  return runtime.setGlobalOption(keyOrOptions, value);
8590
8921
  }
8591
- if (arguments.length === 1 && (0, import_utils30.isPlainObject)(keyOrOptions)) {
8922
+ if (arguments.length === 1 && (0, import_utils32.isPlainObject)(keyOrOptions)) {
8592
8923
  return runtime.setGlobalOptions(keyOrOptions);
8593
8924
  }
8594
8925
  };