@web-ts-toolkit/access-router 0.45.0 → 0.46.0

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