@web-ts-toolkit/access-router 0.55.0 → 0.57.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
@@ -2140,10 +2140,34 @@ var init_field_catalog = __esm({
2140
2140
  }
2141
2141
  });
2142
2142
 
2143
+ // src/acl/populate-target-configuration.ts
2144
+ import { isPlainObject as isPlainObject12 } from "@web-ts-toolkit/utils";
2145
+ function capturePopulateTargetConfiguration(runtime, modelName, source, proposedOptions) {
2146
+ const label = `Populate target "${modelName}" for ${source}`;
2147
+ if (!proposedOptions && !runtime.hasModel(modelName))
2148
+ authorizationError(
2149
+ `${label} must be configured first in this runtime; register its Mongoose model and setModelOptions with an object permissionSchema`
2150
+ );
2151
+ const model = runtime.getModelInstance(modelName);
2152
+ if (!model) authorizationError(`${label} requires an actual registered Mongoose model in this runtime`);
2153
+ const options = proposedOptions ?? runtime.getModelOptions(modelName);
2154
+ const permissionSchema = options.permissionSchema === void 0 ? runtime.getDefaultModelOption("permissionSchema") : options.permissionSchema;
2155
+ if (!isPlainObject12(permissionSchema))
2156
+ authorizationError(
2157
+ `${label} requires an effective object permissionSchema; configure the model or runtime model defaults ({} explicitly closes business fields)`
2158
+ );
2159
+ return { model, options: { ...options, permissionSchema } };
2160
+ }
2161
+ var init_populate_target_configuration = __esm({
2162
+ "src/acl/populate-target-configuration.ts"() {
2163
+ init_authorization_snapshot();
2164
+ }
2165
+ });
2166
+
2143
2167
  // src/acl/virtual-populate-dependencies.ts
2144
- import { get as get4, isPlainObject as isPlainObject12 } from "@web-ts-toolkit/utils";
2168
+ import { get as get4, isPlainObject as isPlainObject13 } from "@web-ts-toolkit/utils";
2145
2169
  function record(value, label) {
2146
- if (!isPlainObject12(value)) return fail(label, "entries must be plain { path, select? } objects");
2170
+ if (!isPlainObject13(value)) return fail(label, "entries must be plain { path, select? } objects");
2147
2171
  for (const key of Reflect.ownKeys(value)) {
2148
2172
  const property = Object.getOwnPropertyDescriptor(value, key);
2149
2173
  if (typeof key !== "string" || !property.enumerable || !("value" in property))
@@ -2231,7 +2255,9 @@ function normalizeSelection(select, catalog, label) {
2231
2255
  includeId: selection.includes("_id")
2232
2256
  };
2233
2257
  }
2234
- function captureVirtualPopulateDependencies(runtime, modelName, options) {
2258
+ function captureVirtualPopulateDependencies(runtime, modelName, options, globalOptions = runtime.getGlobalOptions()) {
2259
+ const requireOperationAccessForPopulateDependencies = globalOptions.requireOperationAccessForPopulateDependencies !== false;
2260
+ const requireOperationAccessForPopulateQueries = globalOptions.requireOperationAccessForPopulateQueries !== false;
2235
2261
  const model = runtime.getModelInstance(modelName);
2236
2262
  const targets = /* @__PURE__ */ new Map();
2237
2263
  const scopes = [];
@@ -2271,7 +2297,12 @@ function captureVirtualPopulateDependencies(runtime, modelName, options) {
2271
2297
  label,
2272
2298
  `target "${ref}" requires a registered same-connection non-discriminator ObjectId model bound to the source ref`
2273
2299
  );
2274
- const targetOptions = ref === modelName ? options : runtime.getModelOptions(ref);
2300
+ const { options: targetOptions } = capturePopulateTargetConfiguration(
2301
+ runtime,
2302
+ ref,
2303
+ `dependsOnPopulate virtual "${label}"`,
2304
+ ref === modelName ? options : void 0
2305
+ );
2275
2306
  const defaults = runtime.getDefaultModelOptions();
2276
2307
  const readOption = (key) => resolveNestedOption((path2, fallback) => get4(targetOptions, path2, fallback), key, get4(defaults, key));
2277
2308
  const documentPermissionField = targetOptions.documentPermissionField ?? defaults.documentPermissionField;
@@ -2280,6 +2311,9 @@ function captureVirtualPopulateDependencies(runtime, modelName, options) {
2280
2311
  {
2281
2312
  catalog,
2282
2313
  documentPermissionField,
2314
+ requireOperationAccessForPopulateDependencies,
2315
+ requireOperationAccessForPopulateQueries,
2316
+ permissionSchema: targetOptions.permissionSchema,
2283
2317
  operationAccess: { read: readOption("operationAccess.read") },
2284
2318
  baseFilter: { read: readOption("baseFilter.read") },
2285
2319
  overrideFilter: { read: readOption("overrideFilter.read") },
@@ -2328,13 +2362,13 @@ function captureVirtualPopulateDependencies(runtime, modelName, options) {
2328
2362
  );
2329
2363
  continue;
2330
2364
  }
2331
- if (!isPlainObject12(raw)) continue;
2365
+ if (!isPlainObject13(raw)) continue;
2332
2366
  const entry = raw;
2333
2367
  const label = join(sourcePrefix, name);
2334
2368
  captureList(entry.dependsOnPopulate, label);
2335
2369
  if (!("get" in entry))
2336
2370
  for (const access of accesses) {
2337
- if (isPlainObject12(entry[access]))
2371
+ if (isPlainObject13(entry[access]))
2338
2372
  captureList(entry[access].dependsOnPopulate, `${label}.${access}`);
2339
2373
  }
2340
2374
  const byAccess = {};
@@ -2359,8 +2393,8 @@ function captureVirtualPopulateDependencies(runtime, modelName, options) {
2359
2393
  visit(options.virtuals ?? {}, model?.schema, "", [], !!model && varyingModel(model));
2360
2394
  return Object.freeze({ scopes: Object.freeze(scopes) });
2361
2395
  }
2362
- function captureVirtualPopulateSnapshot(runtime, modelName, options) {
2363
- const snapshot = captureVirtualPopulateDependencies(runtime, modelName, options);
2396
+ function captureVirtualPopulateSnapshot(runtime, modelName, options, globalOptions = runtime.getGlobalOptions()) {
2397
+ const snapshot = captureVirtualPopulateDependencies(runtime, modelName, options, globalOptions);
2364
2398
  const token = () => {
2365
2399
  };
2366
2400
  snapshots.set(token, snapshot);
@@ -2392,6 +2426,7 @@ var init_virtual_populate_dependencies = __esm({
2392
2426
  init_authorization_snapshot();
2393
2427
  init_field_catalog();
2394
2428
  init_virtual_descriptor();
2429
+ init_populate_target_configuration();
2395
2430
  elementType = (type) => type.$embeddedSchemaType ?? type.embeddedSchemaType ?? type.caster;
2396
2431
  join = (prefix, path) => prefix ? `${prefix}.${path}` : path;
2397
2432
  within = (path, parent) => path === parent || path.startsWith(`${parent}.`);
@@ -2416,7 +2451,7 @@ import {
2416
2451
  isBoolean,
2417
2452
  isFunction as isFunction2,
2418
2453
  isNil as isNil2,
2419
- isPlainObject as isPlainObject13,
2454
+ isPlainObject as isPlainObject14,
2420
2455
  isString as isString4,
2421
2456
  keys,
2422
2457
  normalizeUrlPath,
@@ -2483,7 +2518,7 @@ var init_runtime = __esm({
2483
2518
  const modelPermissionKeys = {};
2484
2519
  forEach2(schemaKeys, (schemaKey) => {
2485
2520
  const schemaRule = permissionSchema[schemaKey];
2486
- const ruleEntries = isPlainObject13(schemaRule) ? Object.entries(schemaRule) : FIELD_ACCESS_KEYS.map((accessKey) => [accessKey, schemaRule]);
2521
+ const ruleEntries = isPlainObject14(schemaRule) ? Object.entries(schemaRule) : FIELD_ACCESS_KEYS.map((accessKey) => [accessKey, schemaRule]);
2487
2522
  for (let x = 0; x < ruleEntries.length; x++) {
2488
2523
  const [accessKey, value] = ruleEntries[x];
2489
2524
  if (!isArray5(globalPermissionKeys[accessKey])) {
@@ -2524,7 +2559,7 @@ var init_runtime = __esm({
2524
2559
  VIRTUAL_RESERVED_NAMES = /* @__PURE__ */ new Set(["_id", "__v", "id"]);
2525
2560
  VIRTUAL_DANGEROUS_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
2526
2561
  VIRTUAL_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_$]*$/;
2527
- isVirtualContainerValue2 = (value) => isPlainObject13(value) && "sub" in value;
2562
+ isVirtualContainerValue2 = (value) => isPlainObject14(value) && "sub" in value;
2528
2563
  virtualFullPath = (prefix, name) => prefix ? `${prefix}.${name}` : name;
2529
2564
  virtualPathsOverlap = (virtualPath, permissionField) => {
2530
2565
  if (!virtualPath || !permissionField) return false;
@@ -2619,7 +2654,7 @@ var init_runtime = __esm({
2619
2654
  };
2620
2655
  validateVirtualLeafValue = (value, entryLabel, storedFields, siblingVirtualNames, sharedDependsOn) => {
2621
2656
  if (isFunction2(value)) return;
2622
- if (!isPlainObject13(value)) {
2657
+ if (!isPlainObject14(value)) {
2623
2658
  throw new Error(`Virtual ${entryLabel}: descriptor must be a getter function or { get, dependsOn }`);
2624
2659
  }
2625
2660
  const record3 = value;
@@ -2665,7 +2700,7 @@ var init_runtime = __esm({
2665
2700
  `Virtual ${scopeLabel}${name}: scope-container must only hold "sub" (embedded container "${name}" is not a virtual-leaf collision)`
2666
2701
  );
2667
2702
  }
2668
- if (!isPlainObject13(record4.sub)) {
2703
+ if (!isPlainObject14(record4.sub)) {
2669
2704
  throw new Error(`Virtual ${scopeLabel}${name}: scope-container "sub" must be a plain object`);
2670
2705
  }
2671
2706
  continue;
@@ -2676,7 +2711,7 @@ var init_runtime = __esm({
2676
2711
  );
2677
2712
  }
2678
2713
  if (isFunction2(raw)) continue;
2679
- if (!isPlainObject13(raw)) {
2714
+ if (!isPlainObject14(raw)) {
2680
2715
  throw new Error(
2681
2716
  `Virtual ${scopeLabel}${name}: malformed descriptor (expected getter, { get, dependsOn }, or per-access record)`
2682
2717
  );
@@ -2730,7 +2765,7 @@ var init_runtime = __esm({
2730
2765
  };
2731
2766
  validateVirtualsTree = (model, virtuals, documentPermissionField, modelName) => {
2732
2767
  if (virtuals === void 0 || virtuals === null) return;
2733
- if (!isPlainObject13(virtuals)) {
2768
+ if (!isPlainObject14(virtuals)) {
2734
2769
  throw new Error(`Virtuals for model "${modelName}": virtuals must be a plain object mapping names to descriptors`);
2735
2770
  }
2736
2771
  const rootSchema = model?.schema ?? null;
@@ -2782,7 +2817,8 @@ var init_runtime = __esm({
2782
2817
  {
2783
2818
  requestPermissionField: "_permissions",
2784
2819
  globalPermissions: () => ({}),
2785
- requireRegisteredPopulateModels: true,
2820
+ requireOperationAccessForPopulateQueries: true,
2821
+ requireOperationAccessForPopulateDependencies: true,
2786
2822
  logger: defaultLogger,
2787
2823
  requestComplexity: defaultRequestComplexity
2788
2824
  },
@@ -2955,9 +2991,18 @@ var init_runtime = __esm({
2955
2991
  }
2956
2992
  /** Shallowly replace supplied global values; nested objects such as requestComplexity are not deep-merged. */
2957
2993
  setGlobalOptions(options) {
2958
- this.globalOptions.assign(options);
2994
+ const next = { ...options };
2995
+ if ("requireOperationAccessForPopulateQueries" in next)
2996
+ next.requireOperationAccessForPopulateQueries = next.requireOperationAccessForPopulateQueries !== false;
2997
+ if ("requireOperationAccessForPopulateDependencies" in next)
2998
+ next.requireOperationAccessForPopulateDependencies = next.requireOperationAccessForPopulateDependencies !== false;
2999
+ this.globalOptions.assign(next);
2959
3000
  }
2960
3001
  setGlobalOption(key, value) {
3002
+ if (key === "requireOperationAccessForPopulateQueries" || key === "requireOperationAccessForPopulateDependencies") {
3003
+ this.globalOptions.set(key, value !== false);
3004
+ return;
3005
+ }
2961
3006
  this.globalOptions.set(key, value);
2962
3007
  }
2963
3008
  getGlobalOptions() {
@@ -3078,10 +3123,10 @@ var init_runtime = __esm({
3078
3123
  const segments = nestedPath.split(".");
3079
3124
  let cursor = cloned;
3080
3125
  for (let i = 0; i < segments.length - 1; i++) {
3081
- if (!isPlainObject13(cursor)) break;
3126
+ if (!isPlainObject14(cursor)) break;
3082
3127
  cursor = cursor[segments[i]];
3083
3128
  }
3084
- if (isPlainObject13(cursor)) {
3129
+ if (isPlainObject14(cursor)) {
3085
3130
  delete cursor[segments[segments.length - 1]];
3086
3131
  }
3087
3132
  proposedVirtuals = cloned;
@@ -3573,7 +3618,6 @@ function normalizePolicy(value, applicability, configuration, targets) {
3573
3618
  const matches = targets.filter((target2) => target2.path === path);
3574
3619
  if (matches.length !== 1) authorizationError(`unused or ambiguous related path: ${path}`);
3575
3620
  const target = matches[0];
3576
- if (target.kind === "passthrough") authorizationError(`related policy requires a registered target: ${path}`);
3577
3621
  const child = authorizationRecord(entry, `related.${path}`);
3578
3622
  assertAuthorizationKeys(
3579
3623
  child,
@@ -4083,7 +4127,7 @@ var init_field_authorization = __esm({
4083
4127
  });
4084
4128
 
4085
4129
  // src/acl/virtual-projection.ts
4086
- import { get as get6, isArray as isArray7, isPlainObject as isPlainObject15 } from "@web-ts-toolkit/utils";
4130
+ import { get as get6, isArray as isArray7, isPlainObject as isPlainObject16 } from "@web-ts-toolkit/utils";
4087
4131
  function planVirtualProjection(args) {
4088
4132
  if (args.snapshot.catalog) return planCatalogProjection(args);
4089
4133
  const {
@@ -4317,15 +4361,15 @@ var init_virtual_projection = __esm({
4317
4361
  init_virtual_populate_dependencies();
4318
4362
  init_virtual_descriptor();
4319
4363
  getScopedPermissionSchema = (root, scopePath) => {
4320
- if (!root || !isPlainObject15(root)) return null;
4364
+ if (!root || !isPlainObject16(root)) return null;
4321
4365
  if (scopePath.length === 0) return root;
4322
4366
  const scoped = get6(root, scopePath.join("."));
4323
- return isPlainObject15(scoped) ? scoped : null;
4367
+ return isPlainObject16(scoped) ? scoped : null;
4324
4368
  };
4325
4369
  resolveAlwaysSelectForAccess = (raw, access) => {
4326
4370
  if (!raw) return [];
4327
4371
  if (isArray7(raw)) return [...raw];
4328
- if (isPlainObject15(raw)) {
4372
+ if (isPlainObject16(raw)) {
4329
4373
  const record3 = raw;
4330
4374
  const exact = record3[access];
4331
4375
  if (isArray7(exact)) return [...exact];
@@ -4599,15 +4643,22 @@ function getCapturedAuthorizationOption(configuration, key) {
4599
4643
  const defaultValue = key.startsWith("operationAccess.") ? get7(configuration.operationAccessDefaults, key.slice("operationAccess.".length)) : void 0;
4600
4644
  return resolveNestedOption((path, fallback) => get7(configuration, path, fallback), key, defaultValue);
4601
4645
  }
4602
- function captureModelAuthorizationConfiguration(runtime, modelName, options = runtime.getModelOptions(modelName)) {
4646
+ function captureModelAuthorizationConfiguration(runtime, modelName, options = runtime.getModelOptions(modelName), globalOptions = runtime.getGlobalOptions()) {
4603
4647
  const model = runtime.getModelInstance(modelName);
4604
4648
  if (!model) return authorizationError(`unknown receiving model: ${modelName}`);
4605
4649
  const virtuals = options.virtuals;
4606
4650
  return copyAuthorizationValue(
4607
4651
  {
4608
4652
  catalog: captureAuthorizationFieldCatalog(model.schema, virtuals, options.documentPermissionField),
4609
- virtualPopulateSnapshot: captureVirtualPopulateSnapshot(runtime, modelName, options),
4610
- permissionSchema: options.permissionSchema,
4653
+ virtualPopulateSnapshot: captureVirtualPopulateSnapshot(
4654
+ runtime,
4655
+ modelName,
4656
+ options,
4657
+ globalOptions
4658
+ ),
4659
+ requireOperationAccessForPopulateDependencies: globalOptions.requireOperationAccessForPopulateDependencies !== false,
4660
+ requireOperationAccessForPopulateQueries: globalOptions.requireOperationAccessForPopulateQueries !== false,
4661
+ permissionSchema: options.permissionSchema === void 0 ? runtime.getDefaultModelOption("permissionSchema") : options.permissionSchema,
4611
4662
  virtuals,
4612
4663
  alwaysSelectFields: options.alwaysSelectFields,
4613
4664
  modelPermissionPrefix: options.modelPermissionPrefix,
@@ -4673,15 +4724,15 @@ import {
4673
4724
  isBoolean as isBoolean2,
4674
4725
  isEmpty as isEmpty2,
4675
4726
  isFunction as isFunction3,
4676
- isPlainObject as isPlainObject16,
4727
+ isPlainObject as isPlainObject17,
4677
4728
  isString as isString5
4678
4729
  } from "@web-ts-toolkit/utils";
4679
4730
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
4680
4731
  function isAndFilter(filter2) {
4681
- return !!filter2 && isPlainObject16(filter2) && Object.keys(filter2).length === 1 && isArray8(filter2.$and);
4732
+ return !!filter2 && isPlainObject17(filter2) && Object.keys(filter2).length === 1 && isArray8(filter2.$and);
4682
4733
  }
4683
4734
  function isMergeableClause(filter2) {
4684
- return !!filter2 && isPlainObject16(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
4735
+ return !!filter2 && isPlainObject17(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
4685
4736
  }
4686
4737
  function optimizeAndFilter(clausesInput) {
4687
4738
  const clauses = [];
@@ -4702,7 +4753,7 @@ function optimizeAndFilter(clausesInput) {
4702
4753
  const entries = Array.from(value, dedupeKey);
4703
4754
  return entries.every((entry) => entry !== void 0) ? `array:${JSON.stringify(entries)}` : void 0;
4704
4755
  }
4705
- if (isPlainObject16(value)) {
4756
+ if (isPlainObject17(value)) {
4706
4757
  const entries = Object.keys(value).sort().map((key) => [key, dedupeKey(value[key])]);
4707
4758
  return entries.every(([, entry]) => entry !== void 0) ? `object:${JSON.stringify(entries)}` : void 0;
4708
4759
  }
@@ -4750,7 +4801,7 @@ function optimizeAndFilter(clausesInput) {
4750
4801
  }
4751
4802
  function normalizeFilter(filter2) {
4752
4803
  if (filter2 === false) return false;
4753
- if (!filter2 || !isPlainObject16(filter2) || isEmpty2(filter2)) return null;
4804
+ if (!filter2 || !isPlainObject17(filter2) || isEmpty2(filter2)) return null;
4754
4805
  if (!isAndFilter(filter2)) {
4755
4806
  return filter2;
4756
4807
  }
@@ -4828,7 +4879,7 @@ async function setRequestPermissions(req) {
4828
4879
  if (hasFieldValue && owner === void 0) return;
4829
4880
  const globalPermissions = getGlobalOption("globalPermissions");
4830
4881
  const permissions = isFunction3(globalPermissions) ? await globalPermissions.call(req, req) : void 0;
4831
- if (isPlainObject16(permissions)) req[requestPermissionField] = permissions;
4882
+ if (isPlainObject17(permissions)) req[requestPermissionField] = permissions;
4832
4883
  else if (isArray8(permissions)) req[requestPermissionField] = arrayToRecord(permissions);
4833
4884
  else if (isString5(permissions)) req[requestPermissionField] = { [permissions]: true };
4834
4885
  else req[requestPermissionField] = {};
@@ -5227,7 +5278,7 @@ var init_filter_resolution = __esm({
5227
5278
  // src/acl/virtual-populate-execution.ts
5228
5279
  import { Document as Document4, mongo as mongo4 } from "mongoose";
5229
5280
  import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
5230
- import { isPlainObject as isPlainObject19 } from "@web-ts-toolkit/utils";
5281
+ import { isPlainObject as isPlainObject20 } from "@web-ts-toolkit/utils";
5231
5282
  function projectVirtualPopulateView(source, selection, structurePaths = []) {
5232
5283
  const root = { children: /* @__PURE__ */ new Map() };
5233
5284
  for (const path of compactPersistedProjection(selection)) {
@@ -5259,8 +5310,8 @@ function equivalentVirtualPopulatePredicate(left, right) {
5259
5310
  }
5260
5311
  if (Array.isArray(left) || Array.isArray(right))
5261
5312
  return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((entry, index) => equivalentVirtualPopulatePredicate(entry, right[index]));
5262
- if (isPlainObject19(left) || isPlainObject19(right)) {
5263
- if (!isPlainObject19(left) || !isPlainObject19(right)) return false;
5313
+ if (isPlainObject20(left) || isPlainObject20(right)) {
5314
+ if (!isPlainObject20(left) || !isPlainObject20(right)) return false;
5264
5315
  const a = Object.keys(left), b = Object.keys(right);
5265
5316
  return a.length === b.length && a.every(
5266
5317
  (key, index) => key === b[index] && equivalentVirtualPopulatePredicate(
@@ -5277,7 +5328,8 @@ function createVirtualPopulateBatch(owner) {
5277
5328
  owner: { ...owner },
5278
5329
  rows: [],
5279
5330
  requests: [],
5280
- rowCache: owner.core && getRequestRowCache(owner.core) || new Cache()
5331
+ rowCache: owner.core && getRequestRowCache(owner.core) || new Cache(),
5332
+ readGuards: /* @__PURE__ */ new Map()
5281
5333
  });
5282
5334
  return batch;
5283
5335
  }
@@ -5326,7 +5378,12 @@ function captureVirtualPopulateRows(batch, inputs, plan, publicPopulate = [], so
5326
5378
  }
5327
5379
  const record3 = storage(value);
5328
5380
  if (!record3) return;
5329
- const occurrence = { address, plan: currentPlan, refs: /* @__PURE__ */ new Map(), dependencies: /* @__PURE__ */ new Map() };
5381
+ const occurrence = {
5382
+ address,
5383
+ plan: currentPlan,
5384
+ refs: /* @__PURE__ */ new Map(),
5385
+ dependencies: /* @__PURE__ */ new Map()
5386
+ };
5330
5387
  row.occurrences.set(addressKey(address), occurrence);
5331
5388
  for (const candidate of currentPlan.candidates) {
5332
5389
  const dependencies = (candidate.populateDependencies ?? []).map((dependency) => {
@@ -5392,6 +5449,27 @@ function referenceIds(original) {
5392
5449
  if (!original.present || original.value == null) return [];
5393
5450
  return (Array.isArray(original.value) ? original.value : [original.value]).filter((id) => id != null);
5394
5451
  }
5452
+ function targetReadAllowed(state, target) {
5453
+ let pending = state.readGuards.get(target.identity);
5454
+ if (!pending) {
5455
+ const { owner } = state;
5456
+ pending = getRequestWorkState(owner.request, owner.runtime).hookGate.run(
5457
+ () => evaluateRouteGuard(
5458
+ owner.request,
5459
+ owner.permissions,
5460
+ getCapturedAuthorizationOption(target.configuration, "operationAccess.read")
5461
+ )
5462
+ ).then(
5463
+ (allowed) => !!allowed,
5464
+ () => {
5465
+ warn("Virtual populated dependency unavailable", { modelName: target.modelName, category: "policy" });
5466
+ return false;
5467
+ }
5468
+ );
5469
+ state.readGuards.set(target.identity, pending);
5470
+ }
5471
+ return pending;
5472
+ }
5395
5473
  function executeVirtualPopulateBatch(batch) {
5396
5474
  const state = batches.get(batch);
5397
5475
  if (!state) throw new Error("Unknown private populate batch");
@@ -5408,14 +5486,8 @@ async function execute(state) {
5408
5486
  if (!pending) {
5409
5487
  pending = (async () => {
5410
5488
  try {
5411
- const allowed = await work.hookGate.run(
5412
- () => evaluateRouteGuard(
5413
- owner.request,
5414
- owner.permissions,
5415
- getCapturedAuthorizationOption(target.configuration, "operationAccess.read")
5416
- )
5417
- );
5418
- if (!allowed) return void 0;
5489
+ if (target.configuration.requireOperationAccessForPopulateDependencies !== false && !await targetReadAllowed(state, target))
5490
+ return void 0;
5419
5491
  const parts = await work.hookGate.run(
5420
5492
  () => resolveAccessFilterParts({
5421
5493
  req: owner.request,
@@ -5642,7 +5714,7 @@ var init_virtual_populate_execution = __esm({
5642
5714
  publicViewIdentities = /* @__PURE__ */ new WeakMap();
5643
5715
  familyRawFetches = /* @__PURE__ */ new WeakMap();
5644
5716
  addressKey = (address) => JSON.stringify(address);
5645
- storage = (value) => value instanceof Document4 ? value._doc : isPlainObject19(value) ? value : void 0;
5717
+ storage = (value) => value instanceof Document4 ? value._doc : isPlainObject20(value) ? value : void 0;
5646
5718
  originalRef = (source, key) => {
5647
5719
  const record3 = storage(source);
5648
5720
  return {
@@ -5655,7 +5727,7 @@ var init_virtual_populate_execution = __esm({
5655
5727
  const option = getCapturedAuthorizationOption(configuration, `${key}.${access}`);
5656
5728
  return key === "operationAccess" || typeof option === "function" ? option : void 0;
5657
5729
  });
5658
- compatible = (a, b) => a.binding.runtime === b.binding.runtime && a.binding.model === b.binding.model && a.binding.schema === b.binding.schema && a.access === b.access && a.requestRead === b.requestRead && // Different captures may share only an equivalent terminal persisted binding/policy.
5730
+ compatible = (a, b) => a.binding.runtime === b.binding.runtime && a.binding.model === b.binding.model && a.binding.schema === b.binding.schema && a.access === b.access && a.requestRead === b.requestRead && a.binding.configuration.requireOperationAccessForPopulateDependencies === b.binding.configuration.requireOperationAccessForPopulateDependencies && // Different captures may share only an equivalent terminal persisted binding/policy.
5659
5731
  equivalentVirtualPopulatePredicate(
5660
5732
  a.binding.configuration.catalog.persistedPaths,
5661
5733
  b.binding.configuration.catalog.persistedPaths
@@ -5669,7 +5741,7 @@ var init_virtual_populate_execution = __esm({
5669
5741
  });
5670
5742
 
5671
5743
  // src/acl/output-field-filter.ts
5672
- import { isPlainObject as isPlainObject20 } from "@web-ts-toolkit/utils";
5744
+ import { isPlainObject as isPlainObject21 } from "@web-ts-toolkit/utils";
5673
5745
  async function resolveOutputDocumentAuthorization(args) {
5674
5746
  const { authorization, binding } = args;
5675
5747
  const { configuration, access } = readServiceAuthorization(authorization, binding);
@@ -5683,7 +5755,7 @@ async function resolveOutputDocumentAuthorization(args) {
5683
5755
  try {
5684
5756
  const run = () => hook.call(this, doc, permissions, args.context);
5685
5757
  const grants = await getRequestWorkState(binding.request, binding.runtime).hookGate.run(run);
5686
- return isPlainObject20(grants) ? grants : {};
5758
+ return isPlainObject21(grants) ? grants : {};
5687
5759
  } catch (error2) {
5688
5760
  warn("docPermissions hook failed; applying empty document permissions", {
5689
5761
  modelName: binding.modelName,
@@ -5798,7 +5870,7 @@ function copyOutputValue(value, plan, path) {
5798
5870
  const visit = (input, current) => {
5799
5871
  if (Array.isArray(input)) return input.map((entry) => visit(entry, current));
5800
5872
  if (input instanceof Map) return visit(Object.fromEntries(input), current);
5801
- if (!isPlainObject20(input)) return deepIsolateValue(input);
5873
+ if (!isPlainObject21(input)) return deepIsolateValue(input);
5802
5874
  const output = {};
5803
5875
  for (const [key, entry] of Object.entries(input)) {
5804
5876
  const child = `${current}.${key}`;
@@ -5814,25 +5886,21 @@ function copyOutputValue(value, plan, path) {
5814
5886
  }
5815
5887
  function copyAuthorizedReference(value, plan, path) {
5816
5888
  if (Array.isArray(value)) return value.map((entry) => copyAuthorizedReference(entry, plan, path));
5817
- if (!isPlainObject20(value)) return deepIsolateValue(value);
5889
+ if (!isPlainObject21(value)) return deepIsolateValue(value);
5818
5890
  const handle = getAssociatedDocumentAuthorization(value, plan.authorization);
5819
5891
  if (handle) {
5820
5892
  const copy = deepIsolateValue(value);
5821
5893
  transferDocumentAuthorizationGraph(value, copy);
5822
5894
  return copy;
5823
5895
  }
5824
- const { relatedTargets } = readServiceAuthorization(plan.authorization, plan.authorizationBinding);
5825
- if (relatedTargets.some((target) => target.path === path && target.kind === "passthrough")) {
5826
- return deepIsolateValue(value);
5827
- }
5828
5896
  return Object.prototype.hasOwnProperty.call(value, "_id") ? deepIsolateValue(value._id) : void 0;
5829
5897
  }
5830
5898
  function retainOutputMetadata(output, source, configuration, scope2) {
5831
5899
  if (scope2.length || configuration.stripPermissionsField) return;
5832
5900
  const parts = (configuration.documentPermissionField ?? "_permissions").split(".");
5833
5901
  let value = source;
5834
- for (const part of parts) value = isPlainObject20(value) ? value[part] : void 0;
5835
- if (!isPlainObject20(value)) return;
5902
+ for (const part of parts) value = isPlainObject21(value) ? value[part] : void 0;
5903
+ if (!isPlainObject21(value)) return;
5836
5904
  const keys2 = configuration.exposedDocPermissionKeys;
5837
5905
  const metadata = Object.fromEntries(
5838
5906
  Object.entries(value).filter(
@@ -5842,7 +5910,7 @@ function retainOutputMetadata(output, source, configuration, scope2) {
5842
5910
  const copy = { ...output };
5843
5911
  let target = copy;
5844
5912
  for (const part of parts.slice(0, -1)) {
5845
- target[part] = isPlainObject20(target[part]) ? { ...target[part] } : {};
5913
+ target[part] = isPlainObject21(target[part]) ? { ...target[part] } : {};
5846
5914
  target = target[part];
5847
5915
  }
5848
5916
  target[parts[parts.length - 1]] = deepIsolateValue(metadata);
@@ -5884,7 +5952,7 @@ async function filterExistingOutput(source, plan, handle, associated) {
5884
5952
  for (const entry of value2) values.push(await one(entry) ?? null);
5885
5953
  return values;
5886
5954
  }
5887
- if (!isPlainObject20(value2)) return value2 === null ? null : void 0;
5955
+ if (!isPlainObject21(value2)) return value2 === null ? null : void 0;
5888
5956
  const childHandle = associated ? getAssociatedDocumentAuthorization(value2) : void 0;
5889
5957
  let child = scoped;
5890
5958
  if (childHandle) {
@@ -5964,7 +6032,7 @@ async function filterModelOutputForHelper(args) {
5964
6032
  const plan = helperPlan(normalized.authorization, binding);
5965
6033
  if (args.doc == null) return { output: args.doc, fields: [] };
5966
6034
  const source = isolateDocForOutput(args.doc);
5967
- if (!isPlainObject20(source)) return authorizationError("output helper requires one plain or hydrated document");
6035
+ if (!isPlainObject21(source)) return authorizationError("output helper requires one plain or hydrated document");
5968
6036
  const knownOutput = !!args.doc && typeof args.doc === "object" && isKnownAuthorizationOutput(args.doc);
5969
6037
  const handle = await resolveOutputDocumentAuthorization({
5970
6038
  authorization: normalized.authorization,
@@ -6059,7 +6127,7 @@ __export(finalize_model_output_exports, {
6059
6127
  finalizeModelOutput: () => finalizeModelOutput,
6060
6128
  finalizeModelOutputs: () => finalizeModelOutputs
6061
6129
  });
6062
- import { get as get9, isPlainObject as isPlainObject21, set as set7 } from "@web-ts-toolkit/utils";
6130
+ import { get as get9, isPlainObject as isPlainObject22, set as set7 } from "@web-ts-toolkit/utils";
6063
6131
  async function finalizeSingleDoc(args, input) {
6064
6132
  const scopePath = args.scopePath;
6065
6133
  const plan = args.plan;
@@ -6075,8 +6143,8 @@ async function finalizeSingleDoc(args, input) {
6075
6143
  const privateRow = args.virtualPopulateRow ?? (original && typeof original === "object" ? getVirtualPopulateRow(original) : void 0);
6076
6144
  const privateAddress = args.virtualPopulateAddress ?? [];
6077
6145
  const isolated = isolateDocForOutput(input);
6078
- const output = isPlainObject21(isolated) ? isolated : {};
6079
- if (captured && isPlainObject21(input))
6146
+ const output = isPlainObject22(isolated) ? isolated : {};
6147
+ if (captured && isPlainObject22(input))
6080
6148
  for (const key of Object.keys(output)) {
6081
6149
  transferDocumentAuthorizationGraph(input[key], output[key]);
6082
6150
  }
@@ -6131,7 +6199,7 @@ async function finalizeSingleDoc(args, input) {
6131
6199
  );
6132
6200
  continue;
6133
6201
  }
6134
- if (captured && !isPlainObject21(rawContainer) && !Array.isArray(rawContainer)) {
6202
+ if (captured && !isPlainObject22(rawContainer) && !Array.isArray(rawContainer)) {
6135
6203
  delete output[containerName];
6136
6204
  continue;
6137
6205
  }
@@ -6155,7 +6223,7 @@ async function finalizeSingleDoc(args, input) {
6155
6223
  const finalizeOneChild = async (rawChild, address) => {
6156
6224
  if (captured && Array.isArray(rawChild))
6157
6225
  return mapWithConcurrencyLimit(rawChild, limit, (entry, index) => finalizeOneChild(entry, [...address, index]));
6158
- if (!isPlainObject21(rawChild) && !Array.isArray(rawChild)) return captured ? null : deepIsolateValue(rawChild);
6226
+ if (!isPlainObject22(rawChild) && !Array.isArray(rawChild)) return captured ? null : deepIsolateValue(rawChild);
6159
6227
  if (args.traversal?.finalizeEmbeddedChildren) {
6160
6228
  return args.traversal.finalizeEmbeddedChildren({
6161
6229
  containerName,
@@ -6181,7 +6249,7 @@ async function finalizeSingleDoc(args, input) {
6181
6249
  (entry, index) => finalizeOneChild(entry, [...privateAddress, containerName, index])
6182
6250
  );
6183
6251
  output[containerName] = finalized;
6184
- } else if (isPlainObject21(rawContainer)) {
6252
+ } else if (isPlainObject22(rawContainer)) {
6185
6253
  output[containerName] = await finalizeOneChild(rawContainer, [
6186
6254
  ...privateAddress,
6187
6255
  containerName
@@ -6225,7 +6293,7 @@ async function finalizeSingleDoc(args, input) {
6225
6293
  related: target.plan.relatedPlans?.length ? (args.related ?? []).filter((entry) => entry.path.startsWith(`${target.path}.`)) : []
6226
6294
  };
6227
6295
  const finalizeOneRelated = async (rawChild, originalChild = rawChild) => {
6228
- if (!isPlainObject21(rawChild) && !Array.isArray(rawChild)) return rawChild;
6296
+ if (!isPlainObject22(rawChild) && !Array.isArray(rawChild)) return rawChild;
6229
6297
  if (typeof rawChild?._bsontype === "string") return rawChild;
6230
6298
  const childOriginal = getIncludeOriginal(originalChild);
6231
6299
  const childRow = childOriginal && typeof childOriginal === "object" ? getVirtualPopulateRow(childOriginal) : void 0;
@@ -6246,7 +6314,7 @@ async function finalizeSingleDoc(args, input) {
6246
6314
  limit,
6247
6315
  (entry, index) => finalizeOneRelated(entry, Array.isArray(originalRelated) ? originalRelated[index] : void 0)
6248
6316
  );
6249
- } else if (isPlainObject21(rawRelated)) {
6317
+ } else if (isPlainObject22(rawRelated)) {
6250
6318
  finalized = await finalizeOneRelated(rawRelated, originalRelated);
6251
6319
  } else {
6252
6320
  continue;
@@ -6447,20 +6515,20 @@ var init_finalize_model_output = __esm({
6447
6515
  init_field_catalog();
6448
6516
  init_virtual_populate_execution();
6449
6517
  init_include_output();
6450
- isVirtualContainerValue3 = (value) => isPlainObject21(value) && "sub" in value;
6518
+ isVirtualContainerValue3 = (value) => isPlainObject22(value) && "sub" in value;
6451
6519
  getScopeVirtualsObject2 = (rootVirtuals, scopePath) => {
6452
- if (!rootVirtuals || !isPlainObject21(rootVirtuals)) return null;
6520
+ if (!rootVirtuals || !isPlainObject22(rootVirtuals)) return null;
6453
6521
  if (scopePath.length === 0) return rootVirtuals;
6454
6522
  let current = rootVirtuals;
6455
6523
  for (let i = 0; i < scopePath.length; i += 2) {
6456
6524
  const field = scopePath[i];
6457
6525
  const subMarker = scopePath[i + 1];
6458
6526
  if (!field || subMarker !== "sub") return null;
6459
- if (!isPlainObject21(current)) return null;
6527
+ if (!isPlainObject22(current)) return null;
6460
6528
  const container = current[field];
6461
6529
  if (!isVirtualContainerValue3(container)) return null;
6462
6530
  current = container.sub;
6463
- if (!isPlainObject21(current)) return null;
6531
+ if (!isPlainObject22(current)) return null;
6464
6532
  }
6465
6533
  return current;
6466
6534
  };
@@ -6473,11 +6541,11 @@ var init_finalize_model_output = __esm({
6473
6541
  return Object.entries(scopeVirtuals).filter(([, v]) => v !== void 0 && !isVirtualContainerValue3(v)).map(([k]) => k);
6474
6542
  };
6475
6543
  getScopedPermissionSchema2 = (root, scopePath) => {
6476
- if (!root || !isPlainObject21(root)) return null;
6544
+ if (!root || !isPlainObject22(root)) return null;
6477
6545
  if (scopePath.length === 0) return root;
6478
6546
  try {
6479
6547
  const scoped = get9(root, scopePath.join("."));
6480
- return isPlainObject21(scoped) ? scoped : null;
6548
+ return isPlainObject22(scoped) ? scoped : null;
6481
6549
  } catch {
6482
6550
  return null;
6483
6551
  }
@@ -6536,7 +6604,7 @@ var init_finalize_model_output = __esm({
6536
6604
  const grants = await args.sharedGate.run(
6537
6605
  () => hook.call(args.request, isolatedForHook, globalPerms, scopeContext)
6538
6606
  );
6539
- const normalized = isPlainObject21(grants) ? grants : {};
6607
+ const normalized = isPlainObject22(grants) ? grants : {};
6540
6608
  scopeContext.docPermissions = normalized;
6541
6609
  return normalized;
6542
6610
  }
@@ -6558,19 +6626,21 @@ var init_finalize_model_output = __esm({
6558
6626
  });
6559
6627
 
6560
6628
  // src/index.ts
6561
- import { isPlainObject as isPlainObject30, isString as isString10, isUndefined as isUndefined4 } from "@web-ts-toolkit/utils";
6629
+ import { isPlainObject as isPlainObject31, isString as isString10, isUndefined as isUndefined4 } from "@web-ts-toolkit/utils";
6562
6630
 
6563
6631
  // src/middleware.ts
6564
6632
  import JsonRouter5 from "@web-ts-toolkit/express-json-router";
6565
- import { isArray as isArray13, isFunction as isFunction7, isPlainObject as isPlainObject26, isString as isString7 } from "@web-ts-toolkit/utils";
6633
+ import { isArray as isArray13, isFunction as isFunction7, isPlainObject as isPlainObject27, isString as isString7 } from "@web-ts-toolkit/utils";
6566
6634
 
6567
6635
  // src/core.ts
6568
6636
  init_public_admission();
6569
6637
  init_options();
6638
+ init_permission();
6570
6639
  import {
6571
6640
  castArray as castArray4,
6572
6641
  compact as compact3,
6573
6642
  forEach as forEach6,
6643
+ get as get15,
6574
6644
  isArray as isArray12,
6575
6645
  isBoolean as isBoolean5,
6576
6646
  isFunction as isFunction6,
@@ -6580,27 +6650,6 @@ import {
6580
6650
  uniq as uniq3
6581
6651
  } from "@web-ts-toolkit/utils";
6582
6652
 
6583
- // src/meta.ts
6584
- init_runtime();
6585
- init_runtime_context();
6586
- var getRuntime5 = () => getActiveRuntime() ?? defaultRuntime;
6587
- var getModelRef = (modelName, refPath) => {
6588
- const direct = getRuntime5().getModelRef(modelName, refPath);
6589
- if (direct) return direct;
6590
- if (!refPath.includes(".")) return null;
6591
- try {
6592
- const runtime = getRuntime5();
6593
- const instance = runtime.getModelInstance?.(
6594
- modelName
6595
- );
6596
- const schemaPath = instance?.schema?.path?.(refPath);
6597
- const ref = schemaPath?.options?.ref ?? schemaPath?.caster?.options?.ref ?? null;
6598
- return typeof ref === "string" && ref.length > 0 ? ref : null;
6599
- } catch {
6600
- return null;
6601
- }
6602
- };
6603
-
6604
6653
  // src/services/base.ts
6605
6654
  init_options();
6606
6655
  init_helpers();
@@ -6616,7 +6665,7 @@ import {
6616
6665
  forEach as forEach3,
6617
6666
  get as get8,
6618
6667
  isArray as isArray9,
6619
- isPlainObject as isPlainObject17,
6668
+ isPlainObject as isPlainObject18,
6620
6669
  set as set6,
6621
6670
  uniq
6622
6671
  } from "@web-ts-toolkit/utils";
@@ -6741,7 +6790,7 @@ init_public_admission();
6741
6790
  // src/correlated-includes.ts
6742
6791
  init_request_complexity();
6743
6792
  init_sort_policy();
6744
- import { cloneDeep, isArray as isArray6, isPlainObject as isPlainObject14 } from "@web-ts-toolkit/utils";
6793
+ import { cloneDeep, isArray as isArray6, isPlainObject as isPlainObject15 } from "@web-ts-toolkit/utils";
6745
6794
  var PARENT_MARKER_KEY = "$parent";
6746
6795
  var ESCAPE_WRAPPER_KEY = "$escape";
6747
6796
  var SUBQUERY_PAYLOAD_KEYS = /* @__PURE__ */ new Set(["$$sq", "$$date"]);
@@ -6752,10 +6801,10 @@ var FIELD_SEGMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_$]*$/;
6752
6801
  var ARRAY_INDEX_SEGMENT_PATTERN = /^(0|[1-9]\d*)$/;
6753
6802
  var ownKeys = (value) => Object.keys(value);
6754
6803
  function isParentRefMarker(value) {
6755
- return isPlainObject14(value) && ownKeys(value).length === 1 && ownKeys(value)[0] === PARENT_MARKER_KEY;
6804
+ return isPlainObject15(value) && ownKeys(value).length === 1 && ownKeys(value)[0] === PARENT_MARKER_KEY;
6756
6805
  }
6757
6806
  function isEscapedParentRef(value) {
6758
- return isPlainObject14(value) && ownKeys(value).length === 1 && ownKeys(value)[0] === ESCAPE_WRAPPER_KEY;
6807
+ return isPlainObject15(value) && ownKeys(value).length === 1 && ownKeys(value)[0] === ESCAPE_WRAPPER_KEY;
6759
6808
  }
6760
6809
  function hasMarkerKey(value) {
6761
6810
  return ownKeys(value).includes(PARENT_MARKER_KEY) || ownKeys(value).includes(ESCAPE_WRAPPER_KEY);
@@ -6779,7 +6828,7 @@ function containsParentMarkerShape(value) {
6779
6828
  seen.add(current);
6780
6829
  return current.some(visit);
6781
6830
  }
6782
- if (isPlainObject14(current)) {
6831
+ if (isPlainObject15(current)) {
6783
6832
  if (seen.has(current)) return false;
6784
6833
  seen.add(current);
6785
6834
  if (hasMarkerKey(current)) return true;
@@ -6809,7 +6858,7 @@ function scanTemplateValueNode(value, path, issues) {
6809
6858
  }
6810
6859
  return;
6811
6860
  }
6812
- if (isPlainObject14(value)) {
6861
+ if (isPlainObject15(value)) {
6813
6862
  if (hasMarkerKey(value)) {
6814
6863
  issues.push({
6815
6864
  message: 'Malformed $parent reference: objects carrying $parent must be exactly { "$parent": "<field>" }',
@@ -6874,7 +6923,7 @@ function scanTemplateObjectEntries(obj, basePath, issues) {
6874
6923
  });
6875
6924
  return;
6876
6925
  }
6877
- if (isPlainObject14(clause)) {
6926
+ if (isPlainObject15(clause)) {
6878
6927
  scanTemplateObjectEntries(clause, clausePath, issues);
6879
6928
  }
6880
6929
  });
@@ -6885,7 +6934,7 @@ function scanTemplateObjectEntries(obj, basePath, issues) {
6885
6934
  }
6886
6935
  function validateCorrelatedFilterTemplate(filter2) {
6887
6936
  const issues = [];
6888
- if (!isPlainObject14(filter2)) {
6937
+ if (!isPlainObject15(filter2)) {
6889
6938
  issues.push({ message: "Correlated include filter must be an object", path: [] });
6890
6939
  return issues;
6891
6940
  }
@@ -6934,7 +6983,7 @@ function collectFilterReferencePaths(filter2) {
6934
6983
  value.forEach(visitValue);
6935
6984
  return;
6936
6985
  }
6937
- if (isPlainObject14(value)) {
6986
+ if (isPlainObject15(value)) {
6938
6987
  if (seen.has(value)) return;
6939
6988
  seen.add(value);
6940
6989
  for (const [key, child] of Object.entries(value)) {
@@ -6980,7 +7029,7 @@ function getOwnPathValue(parent, path) {
6980
7029
  return current;
6981
7030
  }
6982
7031
  function cloneSubstitutedValue(value) {
6983
- if (isPlainObject14(value) || isArray6(value)) return cloneDeep(value);
7032
+ if (isPlainObject15(value) || isArray6(value)) return cloneDeep(value);
6984
7033
  return value;
6985
7034
  }
6986
7035
  function literalParentRefObject(path) {
@@ -7029,7 +7078,7 @@ function resolveObjectEntries(obj, parent) {
7029
7078
  if (isParentRefMarker(clause) || isEscapedParentRef(clause)) {
7030
7079
  throw new CorrelatedReferenceError(`Unsupported $parent reference as a direct ${key} clause`);
7031
7080
  }
7032
- if (!isPlainObject14(clause)) return cloneSubstitutedValue(clause);
7081
+ if (!isPlainObject15(clause)) return cloneSubstitutedValue(clause);
7033
7082
  return resolveObjectEntries(clause, parent);
7034
7083
  });
7035
7084
  continue;
@@ -7058,10 +7107,10 @@ function resolveValueNode(template, parent, bare) {
7058
7107
  }
7059
7108
  const value = getOwnPathValue(parent, path);
7060
7109
  if (value === void 0 || value === null) throw UNRESOLVABLE;
7061
- if (bare && isPlainObject14(value)) return { $eq: cloneDeep(value) };
7110
+ if (bare && isPlainObject15(value)) return { $eq: cloneDeep(value) };
7062
7111
  return cloneSubstitutedValue(value);
7063
7112
  }
7064
- if (isPlainObject14(template)) {
7113
+ if (isPlainObject15(template)) {
7065
7114
  if (hasMarkerKey(template)) {
7066
7115
  throw new CorrelatedReferenceError(
7067
7116
  'Malformed $parent reference: objects carrying $parent must be exactly { "$parent": "<field>" }'
@@ -7103,7 +7152,7 @@ function resolveCorrelatedIdTemplate(id, parent) {
7103
7152
  }
7104
7153
  function resolveCorrelatedFilterTemplate(filter2, parent) {
7105
7154
  try {
7106
- if (!isPlainObject14(filter2) || isParentRefMarker(filter2) || isEscapedParentRef(filter2)) {
7155
+ if (!isPlainObject15(filter2) || isParentRefMarker(filter2) || isEscapedParentRef(filter2)) {
7107
7156
  throw new CorrelatedReferenceError("Correlated include filter must be an object");
7108
7157
  }
7109
7158
  return { status: "resolved", filter: resolveObjectEntries(filter2, parent) };
@@ -7118,7 +7167,7 @@ function validateExpandedCorrelatedOperands(expanded, limits) {
7118
7167
  var MAX_NESTED_INCLUDE_DEPTH = 10;
7119
7168
  function validateCorrelatedArgsShape(args, op, depth) {
7120
7169
  if (args === void 0) return [];
7121
- if (!isPlainObject14(args)) return ["Correlated include args must be an object"];
7170
+ if (!isPlainObject15(args)) return ["Correlated include args must be an object"];
7122
7171
  const record3 = args;
7123
7172
  const allowed = op === "list" ? /* @__PURE__ */ new Set(["select", "sort", "skip", "limit", "page", "pageSize", "include"]) : /* @__PURE__ */ new Set(["select", "sort", "include"]);
7124
7173
  for (const key of Object.keys(record3)) {
@@ -7140,7 +7189,7 @@ function validateCorrelatedArgsShape(args, op, depth) {
7140
7189
  return [];
7141
7190
  }
7142
7191
  function validateCorrelatedIncludeShape(entry, depth = 0) {
7143
- if (!isPlainObject14(entry)) return ["Correlated include must be an object"];
7192
+ if (!isPlainObject15(entry)) return ["Correlated include must be an object"];
7144
7193
  const record3 = entry;
7145
7194
  if (record3.mode !== "correlated") return ['Correlated include mode must be "correlated"'];
7146
7195
  if (typeof record3.model !== "string" || record3.model.length === 0)
@@ -7153,7 +7202,7 @@ function validateCorrelatedIncludeShape(entry, depth = 0) {
7153
7202
  if ("localField" in record3 || "foreignField" in record3) {
7154
7203
  return ["Correlated includes must not carry localField or foreignField"];
7155
7204
  }
7156
- if (record3.options !== void 0 && !(isPlainObject14(record3.options) && Object.keys(record3.options).length === 0)) {
7205
+ if (record3.options !== void 0 && !(isPlainObject15(record3.options) && Object.keys(record3.options).length === 0)) {
7157
7206
  return ["Correlated include options must be absent or empty"];
7158
7207
  }
7159
7208
  const hasId = record3.id !== void 0;
@@ -7175,7 +7224,7 @@ function validateCorrelatedIncludeShape(entry, depth = 0) {
7175
7224
  return validateCorrelatedArgsShape(record3.args, record3.op, depth);
7176
7225
  }
7177
7226
  function isCorrelatedInclude(value) {
7178
- return isPlainObject14(value) && value.mode === "correlated";
7227
+ return isPlainObject15(value) && value.mode === "correlated";
7179
7228
  }
7180
7229
 
7181
7230
  // src/services/base.ts
@@ -7242,6 +7291,11 @@ function modelServiceOptions(receiver, read) {
7242
7291
  if (call?.receiver !== receiver) return read();
7243
7292
  return call.options ??= read();
7244
7293
  }
7294
+ function modelServiceGlobalOptions(receiver, runtime) {
7295
+ const call = dispatch.getStore();
7296
+ if (call?.receiver !== receiver) return runtime.getGlobalOptions();
7297
+ return call.globalOptions ??= runtime.getGlobalOptions();
7298
+ }
7245
7299
 
7246
7300
  // src/services/base.ts
7247
7301
  init_request_work();
@@ -7394,9 +7448,31 @@ function castCountGroups(input, values) {
7394
7448
  // src/services/base.ts
7395
7449
  init_service_authorization();
7396
7450
 
7451
+ // src/meta.ts
7452
+ init_runtime();
7453
+ init_runtime_context();
7454
+ var getRuntime5 = () => getActiveRuntime() ?? defaultRuntime;
7455
+ var getModelRef = (modelName, refPath, runtime = getRuntime5()) => {
7456
+ const direct = runtime.getModelRef(modelName, refPath);
7457
+ if (direct) return direct;
7458
+ if (!refPath.includes(".")) return null;
7459
+ try {
7460
+ const instance = runtime.getModelInstance?.(
7461
+ modelName
7462
+ );
7463
+ const schemaPath = instance?.schema?.path?.(refPath);
7464
+ const element = schemaPath?.$embeddedSchemaType ?? schemaPath?.embeddedSchemaType ?? schemaPath?.caster;
7465
+ const ref = schemaPath?.options?.ref ?? element?.options?.ref ?? null;
7466
+ return typeof ref === "string" && ref.length > 0 ? ref : null;
7467
+ } catch {
7468
+ return null;
7469
+ }
7470
+ };
7471
+
7397
7472
  // src/acl/related-execution.ts
7398
7473
  init_public_admission();
7399
7474
  init_model_output_planning();
7475
+ init_populate_target_configuration();
7400
7476
  init_authorization_snapshot();
7401
7477
 
7402
7478
  // src/acl/service-execution.ts
@@ -7682,7 +7758,7 @@ function staticObjectIdReference(runtime, source, path, target) {
7682
7758
  const scalar = type?.instance === "Array" ? type.$embeddedSchemaType ?? type.embeddedSchemaType ?? type.caster : type;
7683
7759
  return scalar?.instance === "ObjectId" && (type?.options?.ref ?? scalar.options?.ref) === target && type?.options?.refPath === void 0 && scalar.options?.refPath === void 0;
7684
7760
  }
7685
- function captureRelatedTargets(runtime, modelName, args = {}, depth = 0) {
7761
+ function captureRelatedTargets(runtime, modelName, args = {}, depth = 0, globalOptions = runtime.getGlobalOptions()) {
7686
7762
  if (depth > 32) authorizationError("related descriptor depth exceeded");
7687
7763
  assertPublicAdmission(args.populate, "populate");
7688
7764
  assertPublicAdmission(args.include, "include");
@@ -7691,21 +7767,20 @@ function captureRelatedTargets(runtime, modelName, args = {}, depth = 0) {
7691
7767
  for (const entry of entries(args.populate)) {
7692
7768
  const path = typeof entry === "string" ? entry : entry.path;
7693
7769
  if (typeof entry !== "string") assertPopulateAccess(entry.access);
7694
- const model = getModelRef(modelName, path);
7695
- if (model && runtime.hasModel(model))
7770
+ const model = getModelRef(modelName, path, runtime);
7771
+ if (model) {
7772
+ const target = capturePopulateTargetConfiguration(runtime, model, `${modelName}.${path}`);
7696
7773
  targets.push({
7697
7774
  path,
7698
7775
  modelName: model,
7699
- configuration: captureModelAuthorizationConfiguration(runtime, model),
7776
+ configuration: captureModelAuthorizationConfiguration(runtime, model, target.options, globalOptions),
7700
7777
  staticObjectIdReference: staticObjectIdReference(runtime, modelName, path, model)
7701
7778
  });
7702
- else if (model && !runtime.getGlobalOption("requireRegisteredPopulateModels", true)) {
7703
- targets.push({ path, modelName: model, kind: "passthrough", configuration: { catalog: { paths: [] } } });
7704
7779
  }
7705
7780
  }
7706
7781
  for (const entry of entries(args.include)) {
7707
7782
  if (!runtime.hasModel(entry.model)) authorizationError(`Model ${entry.model} not found`);
7708
- const configuration = captureModelAuthorizationConfiguration(runtime, entry.model);
7783
+ const configuration = captureModelAuthorizationConfiguration(runtime, entry.model, void 0, globalOptions);
7709
7784
  const queryIntent = entry.op === "count" ? void 0 : captureModelServiceIntent(
7710
7785
  {
7711
7786
  defaults: configuration.defaults ?? {},
@@ -7731,7 +7806,13 @@ function captureRelatedTargets(runtime, modelName, args = {}, depth = 0) {
7731
7806
  configuration,
7732
7807
  queryIntent,
7733
7808
  operationGuard: runtime.getModelOption(entry.model, `operationAccess.${entry.op}`),
7734
- relatedTargets: captureRelatedTargets(runtime, entry.model, queryIntent?.args ?? entry.args ?? {}, depth + 1)
7809
+ relatedTargets: captureRelatedTargets(
7810
+ runtime,
7811
+ entry.model,
7812
+ queryIntent?.args ?? entry.args ?? {},
7813
+ depth + 1,
7814
+ globalOptions
7815
+ )
7735
7816
  });
7736
7817
  }
7737
7818
  if (new Set(targets.map((target) => target.path)).size !== targets.length)
@@ -7880,20 +7961,20 @@ var copyAuthorizedOutput = (value) => {
7880
7961
  transferDocumentAuthorizationGraph(value, copy);
7881
7962
  return copy;
7882
7963
  };
7883
- var isVirtualContainerValueForGuard = (value) => isPlainObject17(value) && "sub" in value;
7964
+ var isVirtualContainerValueForGuard = (value) => isPlainObject18(value) && "sub" in value;
7884
7965
  var getScopeVirtualsForGuard = (rootVirtuals, scopePath) => {
7885
- if (!rootVirtuals || !isPlainObject17(rootVirtuals)) return null;
7966
+ if (!rootVirtuals || !isPlainObject18(rootVirtuals)) return null;
7886
7967
  if (scopePath.length === 0) return rootVirtuals;
7887
7968
  let current = rootVirtuals;
7888
7969
  for (let i = 0; i < scopePath.length; i += 2) {
7889
7970
  const field = scopePath[i];
7890
7971
  const subMarker = scopePath[i + 1];
7891
7972
  if (!field || subMarker !== "sub") return null;
7892
- if (!isPlainObject17(current)) return null;
7973
+ if (!isPlainObject18(current)) return null;
7893
7974
  const container = current[field];
7894
7975
  if (!isVirtualContainerValueForGuard(container)) return null;
7895
7976
  current = container.sub;
7896
- if (!isPlainObject17(current)) return null;
7977
+ if (!isPlainObject18(current)) return null;
7897
7978
  }
7898
7979
  return current;
7899
7980
  };
@@ -7959,7 +8040,7 @@ var sanitizeSubScopeVirtualKeys = (data, snapshot, sub) => {
7959
8040
  for (const entry of value) stripScope(entry, currentScope);
7960
8041
  return;
7961
8042
  }
7962
- if (!isPlainObject17(value)) return;
8043
+ if (!isPlainObject18(value)) return;
7963
8044
  const record3 = value;
7964
8045
  const leaves = getScopeVirtualLeafNames(snapshot, currentScope);
7965
8046
  for (const leaf of leaves) {
@@ -7972,7 +8053,7 @@ var sanitizeSubScopeVirtualKeys = (data, snapshot, sub) => {
7972
8053
  for (const key of Object.keys(record3)) {
7973
8054
  if (leaves.has(key) || containers.has(key)) continue;
7974
8055
  const child = record3[key];
7975
- if (Array.isArray(child) || isPlainObject17(child)) {
8056
+ if (Array.isArray(child) || isPlainObject18(child)) {
7976
8057
  stripScope(child, currentScope);
7977
8058
  }
7978
8059
  }
@@ -7984,7 +8065,7 @@ var LOGICAL_FILTER_KEYS = /* @__PURE__ */ new Set(["$and", "$or", "$nor"]);
7984
8065
  var isOperatorKey = (key) => key.startsWith("$");
7985
8066
  var REMOVED_VIRTUAL_FILTER = /* @__PURE__ */ Symbol("removed virtual filter clause");
7986
8067
  var rewriteFilterObject = (value, visit) => {
7987
- if (!isPlainObject17(value)) return value;
8068
+ if (!isPlainObject18(value)) return value;
7988
8069
  let changed = false;
7989
8070
  const entries = [];
7990
8071
  for (const [key, child] of Object.entries(value)) {
@@ -8015,7 +8096,7 @@ var resolveVirtualFilterField = (path, scope2) => {
8015
8096
  if (!Object.hasOwn(scope2, segment) || scope2[segment] === void 0) return null;
8016
8097
  const definition = scope2[segment];
8017
8098
  if (!isVirtualContainerValueForGuard(definition)) return REMOVED_VIRTUAL_FILTER;
8018
- scope2 = isPlainObject17(definition.sub) ? definition.sub : null;
8099
+ scope2 = isPlainObject18(definition.sub) ? definition.sub : null;
8019
8100
  inContainer = true;
8020
8101
  }
8021
8102
  return scope2;
@@ -8027,7 +8108,7 @@ var stripVirtualFilterOperator = (key, value, scope2) => {
8027
8108
  }
8028
8109
  if (key === "$all") {
8029
8110
  return rewriteFilterClauses(value, (entry) => {
8030
- if (!isPlainObject17(entry) || Object.keys(entry).length !== 1 || !Object.hasOwn(entry, "$elemMatch")) return entry;
8111
+ if (!isPlainObject18(entry) || Object.keys(entry).length !== 1 || !Object.hasOwn(entry, "$elemMatch")) return entry;
8031
8112
  return rewriteFilterObject(entry, (operator, operand) => stripVirtualFilterOperator(operator, operand, scope2));
8032
8113
  });
8033
8114
  }
@@ -8042,7 +8123,7 @@ var stripVirtualFilterQuery = (value, scope2, element = false) => {
8042
8123
  if (isOperatorKey(key)) return element ? stripVirtualFilterOperator(key, child, scope2) : child;
8043
8124
  const fieldScope = resolveVirtualFilterField(key, scope2);
8044
8125
  if (fieldScope === REMOVED_VIRTUAL_FILTER) return REMOVED_VIRTUAL_FILTER;
8045
- if (!fieldScope || !isPlainObject17(child)) return child;
8126
+ if (!fieldScope || !isPlainObject18(child)) return child;
8046
8127
  if (Object.keys(child).some(isOperatorKey)) {
8047
8128
  return rewriteFilterObject(
8048
8129
  child,
@@ -8066,7 +8147,7 @@ function getCorrelatedTemplateDepth(includes) {
8066
8147
  const visit = (entries, depth) => {
8067
8148
  const list = castArray2(entries).filter(Boolean);
8068
8149
  for (const entry of list) {
8069
- if (!isPlainObject17(entry)) continue;
8150
+ if (!isPlainObject18(entry)) continue;
8070
8151
  max = Math.max(max, depth);
8071
8152
  const nested = entry.args?.include;
8072
8153
  if (nested !== void 0) {
@@ -8095,7 +8176,7 @@ function validateClientFilter(filter2) {
8095
8176
  value.forEach((item, index) => visit(item, `${path}[${index}]`));
8096
8177
  return;
8097
8178
  }
8098
- if (!isPlainObject17(value)) return;
8179
+ if (!isPlainObject18(value)) return;
8099
8180
  Object.entries(value).forEach(([key, child]) => {
8100
8181
  const nextPath = path ? `${path}.${key}` : key;
8101
8182
  if (blockedOperators.has(key)) {
@@ -8377,9 +8458,9 @@ var Base = class {
8377
8458
  const walk = (node, prefix) => {
8378
8459
  for (const [key, value] of Object.entries(node)) {
8379
8460
  if (value === void 0) continue;
8380
- if (isPlainObject17(value) && "sub" in value) {
8461
+ if (isPlainObject18(value) && "sub" in value) {
8381
8462
  const sub = value.sub;
8382
- if (isPlainObject17(sub)) walk(sub, prefix ? `${prefix}.${key}` : key);
8463
+ if (isPlainObject18(sub)) walk(sub, prefix ? `${prefix}.${key}` : key);
8383
8464
  continue;
8384
8465
  }
8385
8466
  out.push(prefix ? `${prefix}.${key}` : key);
@@ -8432,7 +8513,7 @@ var Base = class {
8432
8513
  this.throwClientRequestError("bad_request" /* BadRequest */, `Model ${modelName} not found`);
8433
8514
  }
8434
8515
  for (const entry of entries) {
8435
- if (!isPlainObject17(entry)) continue;
8516
+ if (!isPlainObject18(entry)) continue;
8436
8517
  const models = visited.get(entry) ?? /* @__PURE__ */ new Set();
8437
8518
  if (models.has(modelName)) continue;
8438
8519
  models.add(modelName);
@@ -9051,7 +9132,7 @@ import {
9051
9132
  get as get13,
9052
9133
  isArray as isArray10,
9053
9134
  isBoolean as isBoolean4,
9054
- isPlainObject as isPlainObject24,
9135
+ isPlainObject as isPlainObject25,
9055
9136
  omit,
9056
9137
  set as set10,
9057
9138
  uniq as uniq2,
@@ -9152,7 +9233,7 @@ var model_default = Model;
9152
9233
  // src/acl/distinct-field-authorization.ts
9153
9234
  init_field_authorization();
9154
9235
  init_field_catalog();
9155
- import { isPlainObject as isPlainObject18 } from "@web-ts-toolkit/utils";
9236
+ import { isPlainObject as isPlainObject19 } from "@web-ts-toolkit/utils";
9156
9237
  var schemaVariants = (schema) => Object.values(schema.discriminators ?? {});
9157
9238
  var scalarTypes = /* @__PURE__ */ new Set([
9158
9239
  "String",
@@ -9232,8 +9313,8 @@ function isDistinctFieldAuthorized(args) {
9232
9313
  if (path !== field && !path.startsWith(`${field}.`) && !field.startsWith(`${path}.`)) continue;
9233
9314
  if (path.startsWith(`${field}.`) && (!eligible(path) || !schemaShape(schema, path) || !granted(path)))
9234
9315
  return false;
9235
- if (isPlainObject18(raw) && raw.sub !== void 0) {
9236
- if (!isPlainObject18(raw.sub) || !declaredRulesSafe(raw.sub, path, next)) return false;
9316
+ if (isPlainObject19(raw) && raw.sub !== void 0) {
9317
+ if (!isPlainObject19(raw.sub) || !declaredRulesSafe(raw.sub, path, next)) return false;
9237
9318
  }
9238
9319
  }
9239
9320
  return true;
@@ -9315,7 +9396,9 @@ function prepareSubdocumentOutput(service, sub, operation, virtualAccess, output
9315
9396
  captureRelatedTargets(
9316
9397
  getServiceExecutors(service).binding().runtime,
9317
9398
  service.getServiceModelName(),
9318
- { populate }
9399
+ { populate },
9400
+ 0,
9401
+ configuration
9319
9402
  ),
9320
9403
  void 0,
9321
9404
  configuration,
@@ -9409,7 +9492,7 @@ init_populate_target();
9409
9492
 
9410
9493
  // src/helpers/apply-update.ts
9411
9494
  init_lib();
9412
- import { isPlainObject as isPlainObject22, set as set8 } from "@web-ts-toolkit/utils";
9495
+ import { isPlainObject as isPlainObject23, set as set8 } from "@web-ts-toolkit/utils";
9413
9496
  function applyUpdate(target, data, allowedFields) {
9414
9497
  const wholeFields = /* @__PURE__ */ new Set();
9415
9498
  const partialContainers = /* @__PURE__ */ new Set();
@@ -9429,7 +9512,7 @@ function applyUpdate(target, data, allowedFields) {
9429
9512
  const apply = (values, prefix = "") => {
9430
9513
  for (const [key, value] of Object.entries(values)) {
9431
9514
  const path = prefix ? `${prefix}.${key}` : key;
9432
- if (partialContainers.has(path) && !wholeFields.has(path) && (isPlainObject22(value) || Array.isArray(value))) {
9515
+ if (partialContainers.has(path) && !wholeFields.has(path) && (isPlainObject23(value) || Array.isArray(value))) {
9433
9516
  apply(value, path);
9434
9517
  } else if (isDocument2(target)) {
9435
9518
  target.set(path, value);
@@ -9446,7 +9529,7 @@ init_document();
9446
9529
  init_request_work();
9447
9530
  init_logger_helpers();
9448
9531
  init_request_complexity();
9449
- import { get as get10, isPlainObject as isPlainObject23, set as set9 } from "@web-ts-toolkit/utils";
9532
+ import { get as get10, isPlainObject as isPlainObject24, set as set9 } from "@web-ts-toolkit/utils";
9450
9533
  import { mongo as mongo5 } from "mongoose";
9451
9534
 
9452
9535
  // src/acl/select-resolution.ts
@@ -9563,7 +9646,7 @@ init_field_catalog();
9563
9646
  init_service_authorization();
9564
9647
  var own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
9565
9648
  var inside = (path, root) => path === root || path.startsWith(`${root}.`);
9566
- var record2 = (value) => isPlainObject23(value);
9649
+ var record2 = (value) => isPlainObject24(value);
9567
9650
  function inputValue(value) {
9568
9651
  if (typeof value === "function" || typeof value === "symbol") {
9569
9652
  authorizationError("input values must be persisted data, not functions or symbols");
@@ -10354,10 +10437,12 @@ var Service = class extends Base {
10354
10437
  model: this.model.mongooseModel,
10355
10438
  modelName: this.modelName
10356
10439
  }),
10357
- configuration: () => this.requestRuntime.getModelInstance(this.modelName) ? captureModelAuthorizationConfiguration(this.requestRuntime, this.modelName, {
10358
- ...this.options,
10359
- defaults: this.defaults
10360
- }) : { catalog: { paths: [] }, ...this.options },
10440
+ configuration: () => this.requestRuntime.getModelInstance(this.modelName) ? captureModelAuthorizationConfiguration(
10441
+ this.requestRuntime,
10442
+ this.modelName,
10443
+ { ...this.options, defaults: this.defaults },
10444
+ modelServiceGlobalOptions(this, this.requestRuntime)
10445
+ ) : { catalog: { paths: [] }, ...this.options },
10361
10446
  permissions: () => this.requestCore.getPermissions?.() ?? new permission_default({}),
10362
10447
  parse: (filter2) => this.parseClientData(filter2, this.getCorrelatedExecState().scheduler),
10363
10448
  validate: (filter2, expanded) => {
@@ -10886,12 +10971,13 @@ var Service = class extends Base {
10886
10971
  });
10887
10972
  }
10888
10973
  captureRelated(args, defaults) {
10974
+ const globalOptions = modelServiceGlobalOptions(this, this.requestRuntime);
10889
10975
  const effective = { populate: args?.populate ?? defaults?.populate, include: args?.include ?? defaults?.include };
10890
10976
  if (effective.include) {
10891
10977
  const processed = this.processInclude(effective.include);
10892
10978
  effective.include = [...processed.includes, ...processed.correlatedIncludes];
10893
10979
  }
10894
- return captureRelatedTargets(this.requestRuntime, this.modelName, effective);
10980
+ return captureRelatedTargets(this.requestRuntime, this.modelName, effective, 0, globalOptions);
10895
10981
  }
10896
10982
  /** Canonical population uses the target's retained policy for both fetch and output. */
10897
10983
  async genExecutionPopulate(execution, access, populate) {
@@ -10913,14 +10999,9 @@ var Service = class extends Base {
10913
10999
  modelPermissionPrefix: captured.configuration.modelPermissionPrefix
10914
11000
  });
10915
11001
  if (sourceState === "denied") continue;
10916
- const modelName = getModelRef(this.modelName, descriptor.path);
11002
+ const target = captured.relatedTargets.find((target2) => target2.path === descriptor.path);
11003
+ const modelName = target?.modelName;
10917
11004
  if (!modelName) continue;
10918
- if (!this.requestRuntime.hasModel(modelName)) {
10919
- if (captured.relatedTargets.some((target) => target.path === descriptor.path && target.kind === "passthrough")) {
10920
- result2.push({ path: descriptor.path, select: descriptor.select });
10921
- }
10922
- continue;
10923
- }
10924
11005
  const policy = relatedPolicy(state.authorization, state.binding, descriptor.path);
10925
11006
  const svc = this.requestCore.getPublicService(modelName);
10926
11007
  const child = createRelatedExecution(
@@ -10939,7 +11020,7 @@ var Service = class extends Base {
10939
11020
  if (isExecutionRowDenied(svc, child)) continue;
10940
11021
  const childState = readServiceExecution(svc, child);
10941
11022
  const targetConfiguration = readServiceAuthorization(childState.authorization, childState.binding).configuration;
10942
- if (policy?.operation.mode !== "grant" && !await this.hookGate.run(
11023
+ if (targetConfiguration.requireOperationAccessForPopulateQueries !== false && policy?.operation.mode !== "grant" && !await this.hookGate.run(
10943
11024
  () => evaluateRouteGuard(
10944
11025
  this.req,
10945
11026
  getAuthorizationPermissions(childState.authorization, childState.binding),
@@ -10971,7 +11052,7 @@ var Service = class extends Base {
10971
11052
  schema: childState.binding.model.schema,
10972
11053
  configuration: targetConfiguration,
10973
11054
  requestRead: targetAccess === "read" && policy?.operation.mode !== "grant" && readServiceAuthorization(childState.authorization, childState.binding).authorization.rows.mode === "request",
10974
- staticObjectIdReference: captured.relatedTargets.find((target) => target.path === descriptor.path)?.staticObjectIdReference
11055
+ staticObjectIdReference: captured.relatedTargets.find((target2) => target2.path === descriptor.path)?.staticObjectIdReference
10975
11056
  }
10976
11057
  });
10977
11058
  result2.push(ret);
@@ -10991,7 +11072,7 @@ var Service = class extends Base {
10991
11072
  if (this.isScalarPopulateValue(rawTarget)) return rawTarget;
10992
11073
  const isDoc = (() => {
10993
11074
  try {
10994
- if (isPlainObject24(rawTarget)) return true;
11075
+ if (isPlainObject25(rawTarget)) return true;
10995
11076
  const maybe = rawTarget;
10996
11077
  if (maybe && typeof maybe === "object" && "_doc" in maybe) return true;
10997
11078
  if (maybe && typeof maybe.toObject === "function") return true;
@@ -11015,15 +11096,13 @@ var Service = class extends Base {
11015
11096
  transferDocumentAuthorizationGraph(source, copy2);
11016
11097
  return copy2;
11017
11098
  }
11018
- let targetMongooseModel;
11019
- try {
11020
- const inst = this.requestRuntime.getModelInstance?.(
11021
- meta.targetModelName
11022
- );
11023
- void inst;
11024
- targetMongooseModel = inst;
11025
- } catch {
11026
- targetMongooseModel = parentContext.mongooseModel;
11099
+ let targetMongooseModel = meta.fetchBinding?.model;
11100
+ if (!targetMongooseModel) {
11101
+ try {
11102
+ targetMongooseModel = this.requestRuntime.getModelInstance(meta.targetModelName);
11103
+ } catch {
11104
+ targetMongooseModel = parentContext.mongooseModel;
11105
+ }
11027
11106
  }
11028
11107
  const targetContext = {
11029
11108
  mongooseModel: targetMongooseModel ?? parentContext.mongooseModel,
@@ -13396,7 +13475,7 @@ init_helpers();
13396
13475
 
13397
13476
  // src/processors.ts
13398
13477
  init_permissions_alias();
13399
- import { cloneDeep as cloneDeep5, forEach as forEach5, get as get14, hasOwn as hasOwn3, isArray as isArray11, isPlainObject as isPlainObject25, map, set as set11 } from "@web-ts-toolkit/utils";
13478
+ import { cloneDeep as cloneDeep5, forEach as forEach5, get as get14, hasOwn as hasOwn3, isArray as isArray11, isPlainObject as isPlainObject26, map, set as set11 } from "@web-ts-toolkit/utils";
13400
13479
  function cloneProcessorValue(value) {
13401
13480
  const copy = cloneDeep5(value);
13402
13481
  copyPermissionsAliasGraph(value, copy);
@@ -13436,7 +13515,7 @@ function resolveLeafParents(obj, src) {
13436
13515
  for (let ind = 0; ind < segs.length - 1; ind += 1) {
13437
13516
  const seg = segs[ind];
13438
13517
  targets = targets.reduce((ret, target) => {
13439
- if (!isPlainObject25(target)) return ret;
13518
+ if (!isPlainObject26(target)) return ret;
13440
13519
  const targetObject = target;
13441
13520
  const next = targetObject[seg];
13442
13521
  if (isArray11(next)) {
@@ -13450,7 +13529,7 @@ function resolveLeafParents(obj, src) {
13450
13529
  const leaf = segs[segs.length - 1];
13451
13530
  const pairs = [];
13452
13531
  forEach5(targets, (target) => {
13453
- if (!isPlainObject25(target)) return;
13532
+ if (!isPlainObject26(target)) return;
13454
13533
  pairs.push({ parent: target, leaf });
13455
13534
  });
13456
13535
  return pairs;
@@ -13471,7 +13550,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
13471
13550
  forEach5(segs, (seg, ind) => {
13472
13551
  if (segs.length === ind + 1) {
13473
13552
  forEach5(targets, (target) => {
13474
- if (!isPlainObject25(target)) return;
13553
+ if (!isPlainObject26(target)) return;
13475
13554
  const targetObject = target;
13476
13555
  const leaf = targetObject[seg];
13477
13556
  const sourceForDest = leaf;
@@ -13479,7 +13558,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
13479
13558
  if (isArray11(leaf)) {
13480
13559
  if (leaf.length === 0) {
13481
13560
  depopulated = [];
13482
- } else if (leaf.every((item) => isPlainObject25(item))) {
13561
+ } else if (leaf.every((item) => isPlainObject26(item))) {
13483
13562
  depopulated = map(leaf, idField).map((id, i) => {
13484
13563
  if (id === void 0) {
13485
13564
  failMissingId(`${src}[${i}]`);
@@ -13489,7 +13568,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
13489
13568
  } else {
13490
13569
  return;
13491
13570
  }
13492
- } else if (isPlainObject25(leaf)) {
13571
+ } else if (isPlainObject26(leaf)) {
13493
13572
  const id = get14(leaf, idField);
13494
13573
  if (id === void 0) {
13495
13574
  failMissingId(src);
@@ -13503,7 +13582,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
13503
13582
  });
13504
13583
  } else {
13505
13584
  targets = targets.reduce((ret, target) => {
13506
- if (!isPlainObject25(target)) return ret;
13585
+ if (!isPlainObject26(target)) return ret;
13507
13586
  const targetObject = target;
13508
13587
  const next = targetObject[seg];
13509
13588
  if (isArray11(next)) {
@@ -13609,7 +13688,7 @@ var countPaths = (docObject, operations, options = { mutable: true }) => {
13609
13688
  let count;
13610
13689
  if (isArray11(leafValue)) {
13611
13690
  count = leafValue.length;
13612
- } else if (isPlainObject25(leafValue)) {
13691
+ } else if (isPlainObject26(leafValue)) {
13613
13692
  count = Object.keys(leafValue).length;
13614
13693
  } else if (leafValue === null || typeof leafValue === "undefined") {
13615
13694
  count = 0;
@@ -13687,6 +13766,8 @@ init_core_shared();
13687
13766
  init_virtual_projection();
13688
13767
  init_populate_target();
13689
13768
  init_model_output_planning();
13769
+ init_authorization_snapshot();
13770
+ init_document();
13690
13771
  init_output_field_filter();
13691
13772
  init_request_work();
13692
13773
  var emptyPermissionLeaf = () => ({ $: "_" });
@@ -13797,17 +13878,11 @@ var Core = class {
13797
13878
  return this.genSelect(modelName, access, targetFields, options);
13798
13879
  }
13799
13880
  /** Persisted request-only populate admission; fetch planning is not field authority. */
13800
- async requestPopulateSelect(modelName, access, targetFields = null, scopePath = []) {
13881
+ async requestPopulateSelect(configuration, permissions, access, targetFields = null, scopePath = []) {
13801
13882
  const subPaths = scopePath.flatMap((part) => [part, "sub"]);
13802
- const permissionSchema = getModelOption(modelName, ["permissionSchema"].concat(subPaths).join("."));
13803
- const permissions = this.getGlobalPermissions();
13804
- let virtualNames;
13805
- try {
13806
- const runtime = getActiveRuntime() ?? defaultRuntime;
13807
- virtualNames = runtime.getVirtualNames(modelName, subPaths);
13808
- } catch {
13809
- virtualNames = [];
13810
- }
13883
+ const permissionSchema = get15(configuration, ["permissionSchema", ...subPaths]);
13884
+ const prefix = scopePath.length ? `${scopePath.join(".")}.` : "";
13885
+ const virtualNames = (configuration.catalog.virtualPaths ?? []).filter((path) => path.startsWith(prefix)).map((path) => path.slice(prefix.length)).filter((path) => !path.includes("."));
13811
13886
  return resolveSelectForRequest({
13812
13887
  req: this.req,
13813
13888
  permissionSchema,
@@ -13826,13 +13901,21 @@ var Core = class {
13826
13901
  assertPublicAdmission(_populate, "populate");
13827
13902
  assertPopulateAccess(access);
13828
13903
  if (!_populate) return [];
13829
- let populate = Array.isArray(_populate) ? _populate : [_populate];
13830
- for (const p of populate) {
13904
+ const descriptors = copyAuthorizationValue(Array.isArray(_populate) ? _populate : [_populate], true);
13905
+ for (const p of descriptors) {
13831
13906
  if (!isString6(p)) assertPopulateAccess(p.access);
13832
13907
  }
13833
- populate = compact3(
13908
+ const runtime = this.requestRuntime;
13909
+ const globalOptions = modelServiceGlobalOptions(this, runtime);
13910
+ const relatedTargets = captureRelatedTargets(runtime, modelName, { populate: descriptors }, 0, globalOptions);
13911
+ const sourceConfiguration = captureModelAuthorizationConfiguration(runtime, modelName, void 0, globalOptions);
13912
+ const originalPermissions = this.getGlobalPermissions();
13913
+ const permissions = new permission_default(
13914
+ Object.fromEntries(originalPermissions.keys.map((key) => [key, originalPermissions.has(key)]))
13915
+ );
13916
+ const populate = compact3(
13834
13917
  await Promise.all(
13835
- populate.map(async (p) => {
13918
+ descriptors.map(async (p) => {
13836
13919
  const populateAccess = !isString6(p) && p.access ? p.access : access;
13837
13920
  assertPopulateAccess(populateAccess);
13838
13921
  const originalSelect = isString6(p) ? void 0 : p.select;
@@ -13844,78 +13927,86 @@ var Core = class {
13844
13927
  const pathForSourcePermission = scopePrefix && ret.path.startsWith(`${scopePrefix}.`) ? ret.path.slice(scopePrefix.length + 1) : ret.path;
13845
13928
  const parentPath = pathForSourcePermission.includes(".") ? pathForSourcePermission.split(".")[0] : pathForSourcePermission;
13846
13929
  const allowedParentPaths = await this.requestPopulateSelect(
13847
- modelName,
13930
+ sourceConfiguration,
13931
+ permissions,
13848
13932
  populateAccess,
13849
13933
  [parentPath],
13850
13934
  scopePath
13851
13935
  );
13852
13936
  if (!allowedParentPaths.includes(parentPath)) return null;
13853
- const refModelName = getModelRef(modelName, ret.path);
13854
- if (!refModelName) return null;
13855
- const runtime = getActiveRuntime();
13856
- if (!runtime) return null;
13857
- const requireRegisteredPopulateModels = getGlobalOption("requireRegisteredPopulateModels", true);
13858
- if (!runtime.hasModel(refModelName)) {
13859
- return requireRegisteredPopulateModels ? null : ret;
13860
- }
13861
- const allowedTargetOperation = await this.isAllowed(refModelName, populateAccess);
13862
- if (!allowedTargetOperation) {
13937
+ const target = relatedTargets.find((entry) => entry.path === ret.path);
13938
+ if (!target) return null;
13939
+ const refModelName = target.modelName;
13940
+ const targetConfiguration = target.configuration;
13941
+ if (targetConfiguration.requireOperationAccessForPopulateQueries !== false && !await withCapturedModelOperationGuard(
13942
+ this,
13943
+ refModelName,
13944
+ populateAccess,
13945
+ getCapturedAuthorizationOption(targetConfiguration, `operationAccess.${populateAccess}`),
13946
+ permissions,
13947
+ () => this.isAllowed(refModelName, populateAccess)
13948
+ )) {
13863
13949
  return null;
13864
13950
  }
13865
- ret.select = await this.requestPopulateSelect(refModelName, populateAccess, ret.select);
13866
- const hookInputs = getModelOption(refModelName, `alwaysSelectFields.${populateAccess}`, []);
13867
- ret.select = uniq3([...ret.select, ...stripVirtuals(hookInputs, runtime.getVirtualNames(refModelName))]);
13868
- const filter2 = await this.genFilter(refModelName, populateAccess, null);
13951
+ ret.select = await this.requestPopulateSelect(
13952
+ targetConfiguration,
13953
+ permissions,
13954
+ populateAccess,
13955
+ ret.select
13956
+ );
13957
+ const hookInputs = resolveAlwaysSelectForAccess(
13958
+ targetConfiguration.alwaysSelectFields,
13959
+ populateAccess
13960
+ );
13961
+ ret.select = uniq3([
13962
+ ...ret.select,
13963
+ ...stripVirtuals(hookInputs, [...targetConfiguration.catalog.virtualPaths ?? []])
13964
+ ]);
13965
+ const match = isString6(p) ? {} : p.match ?? {};
13966
+ const filter2 = await resolveAccessFilterForRequest({
13967
+ req: this.req,
13968
+ permissions,
13969
+ cache: this.caches.baseFilter,
13970
+ cacheKey: `${refModelName}_baseFilter_${populateAccess}`,
13971
+ access: populateAccess,
13972
+ filter: deepIsolateValue(match),
13973
+ getOption: (key) => getCapturedAuthorizationOption(targetConfiguration, key)
13974
+ });
13869
13975
  if (filter2 === false) return null;
13870
- ret.match = filter2;
13871
- try {
13872
- const planAccess = populateAccess;
13873
- const targetSnapshot = {
13874
- permissionSchema: getModelOption(refModelName, "permissionSchema", null) ?? null,
13875
- virtuals: getModelOption(refModelName, "virtuals", null) ?? null,
13876
- alwaysSelectFields: getModelOption(refModelName, "alwaysSelectFields", null) ?? null,
13877
- modelPermissionPrefix: getModelOption(refModelName, "modelPermissionPrefix", "") ?? "",
13878
- requireExplicitSelect: getModelOption(refModelName, "requireExplicitSelect", false) ?? false,
13879
- documentPermissionField: getModelOption(refModelName, "documentPermissionField", "_permissions") ?? "_permissions",
13880
- exposedDocPermissionKeys: getModelOption(refModelName, "exposedDocPermissionKeys", void 0),
13881
- stripPermissionsField: getModelOption(refModelName, "stripPermissionsField", false) ?? false,
13882
- disableFieldPermissions: getModelOption(refModelName, "disableFieldPermissions", false) ?? false
13883
- };
13884
- const perms = this.getPermissions();
13885
- const globalForPlan = perms && typeof perms.has === "function" && typeof perms.hasKey === "function" ? perms : { has: () => false, hasKey: () => false };
13886
- const targetPlan = planVirtualProjection({
13887
- receivingModelName: refModelName,
13888
- snapshot: {
13889
- permissionSchema: targetSnapshot.permissionSchema,
13890
- virtuals: targetSnapshot.virtuals,
13891
- alwaysSelectFields: targetSnapshot.alwaysSelectFields,
13892
- modelPermissionPrefix: targetSnapshot.modelPermissionPrefix,
13893
- requireExplicitSelect: targetSnapshot.requireExplicitSelect,
13894
- documentPermissionField: targetSnapshot.documentPermissionField
13895
- },
13896
- virtualAccess: planAccess,
13897
- outputAccess: planAccess,
13898
- docPermissionsAccess: planAccess,
13899
- requestedSelect: originalSelect,
13900
- internalFetch: { baseFields: ["_id"] },
13901
- globalPermissions: globalForPlan
13902
- });
13903
- const merged = uniq3([...normalizeSelect(ret.select), ...targetPlan.virtualOnlyDeps ?? []]).filter(
13904
- (t) => t !== "-_id" && !t.includes(".sub.")
13905
- );
13906
- if (!merged.includes("_id")) merged.push("_id");
13907
- ret.select = merged;
13908
- setPopulateTargetMeta(ret, {
13909
- targetModelName: refModelName,
13910
- virtualAccess: planAccess,
13911
- outputAccess: planAccess,
13912
- docPermissionsAccess: planAccess,
13913
- requestedSelect: originalSelect,
13914
- plan: targetPlan,
13915
- snapshot: targetSnapshot
13916
- });
13917
- } catch {
13918
- }
13976
+ ret.match = deepIsolateValue(intersectFilters(match, filter2));
13977
+ const planAccess = populateAccess;
13978
+ const targetSnapshot = targetConfiguration;
13979
+ const targetPlan = planVirtualProjection({
13980
+ receivingModelName: refModelName,
13981
+ snapshot: {
13982
+ permissionSchema: targetSnapshot.permissionSchema,
13983
+ virtuals: targetSnapshot.virtuals,
13984
+ alwaysSelectFields: targetSnapshot.alwaysSelectFields,
13985
+ modelPermissionPrefix: targetSnapshot.modelPermissionPrefix,
13986
+ requireExplicitSelect: targetSnapshot.requireExplicitSelect,
13987
+ documentPermissionField: targetSnapshot.documentPermissionField
13988
+ },
13989
+ virtualAccess: planAccess,
13990
+ outputAccess: planAccess,
13991
+ docPermissionsAccess: planAccess,
13992
+ requestedSelect: originalSelect,
13993
+ internalFetch: { baseFields: ["_id"] },
13994
+ globalPermissions: permissions
13995
+ });
13996
+ const merged = uniq3([...normalizeSelect(ret.select), ...targetPlan.virtualOnlyDeps ?? []]).filter(
13997
+ (t) => t !== "-_id" && !t.includes(".sub.")
13998
+ );
13999
+ if (!merged.includes("_id")) merged.push("_id");
14000
+ ret.select = merged;
14001
+ setPopulateTargetMeta(ret, {
14002
+ targetModelName: refModelName,
14003
+ virtualAccess: planAccess,
14004
+ outputAccess: planAccess,
14005
+ docPermissionsAccess: planAccess,
14006
+ requestedSelect: originalSelect,
14007
+ plan: targetPlan,
14008
+ snapshot: targetSnapshot
14009
+ });
13919
14010
  return ret;
13920
14011
  })
13921
14012
  )
@@ -14239,12 +14330,12 @@ function guard(condition) {
14239
14330
  const permissions = req[PERMISSIONS];
14240
14331
  let cond = condition;
14241
14332
  let phas = (key) => permissions.has(key);
14242
- if (isPlainObject26(condition)) {
14333
+ if (isPlainObject27(condition)) {
14243
14334
  const { modelName, id, condition: _cond } = condition;
14244
14335
  const svc = req.macl.getPublicService(modelName);
14245
14336
  const select = getModelOption(modelName, `alwaysSelectFields.read`, void 0);
14246
14337
  let _id = isString7(id) ? id : null;
14247
- if (isPlainObject26(id)) {
14338
+ if (isPlainObject27(id)) {
14248
14339
  const { type = "param", key } = id;
14249
14340
  if (type === "param") {
14250
14341
  const paramValue = req.params[key];
@@ -14286,7 +14377,7 @@ import express2 from "express";
14286
14377
 
14287
14378
  // src/routers/model-router.ts
14288
14379
  import JsonRouter8 from "@web-ts-toolkit/express-json-router";
14289
- import { forEach as forEach7, isPlainObject as isPlainObject28, isString as isString8, isUndefined as isUndefined2, normalizeUrlPath as normalizeUrlPath3, padEnd } from "@web-ts-toolkit/utils";
14380
+ import { forEach as forEach7, isPlainObject as isPlainObject29, isString as isString8, isUndefined as isUndefined2, normalizeUrlPath as normalizeUrlPath3, padEnd } from "@web-ts-toolkit/utils";
14290
14381
  init_logger_helpers();
14291
14382
  init_runtime();
14292
14383
  init_runtime_context();
@@ -14420,7 +14511,7 @@ import JsonRouter7 from "@web-ts-toolkit/express-json-router";
14420
14511
 
14421
14512
  // src/validation/common.ts
14422
14513
  import { z as z2 } from "zod";
14423
- import { isPlainObject as isPlainObject27 } from "@web-ts-toolkit/utils";
14514
+ import { isPlainObject as isPlainObject28 } from "@web-ts-toolkit/utils";
14424
14515
 
14425
14516
  // src/validation/public-admission-schema.ts
14426
14517
  init_public_admission();
@@ -14549,7 +14640,7 @@ var correlatedFilterSchema = z2.record(z2.string(), z2.unknown()).superRefine((f
14549
14640
  }
14550
14641
  });
14551
14642
  var correlatedEmptyOptionsSchema = z2.unknown().refine(
14552
- (value) => value === void 0 || isPlainObject27(value) && Object.keys(value).length === 0,
14643
+ (value) => value === void 0 || isPlainObject28(value) && Object.keys(value).length === 0,
14553
14644
  "Correlated include options must be absent or empty"
14554
14645
  ).optional();
14555
14646
  var correlatedPathSchema = z2.string().min(1).superRefine((path, ctx) => {
@@ -14586,7 +14677,7 @@ var correlatedListArgsSchema = z2.object({
14586
14677
  include: z2.lazy(() => correlatedNestedIncludeSchema).optional()
14587
14678
  }).strict().superRefine((args, ctx) => checkArgsMarkers(args.select, args.sort, ctx));
14588
14679
  var correlatedCountArgsSchema = z2.unknown().refine(
14589
- (value) => value === void 0 || isPlainObject27(value) && Object.keys(value).length === 0,
14680
+ (value) => value === void 0 || isPlainObject28(value) && Object.keys(value).length === 0,
14590
14681
  "Count includes accept no args"
14591
14682
  ).optional();
14592
14683
  var correlatedReadByIdSchema = z2.object({
@@ -14639,7 +14730,7 @@ var correlatedNestedIncludeSchema = z2.union([includeItemSchema, z2.array(includ
14639
14730
  var rejectDuplicateCorrelatedPaths = (items, ctx) => {
14640
14731
  const seen = /* @__PURE__ */ new Set();
14641
14732
  items.forEach((item, index) => {
14642
- if (isPlainObject27(item) && item.mode === "correlated" && typeof item.path === "string") {
14733
+ if (isPlainObject28(item) && item.mode === "correlated" && typeof item.path === "string") {
14643
14734
  if (seen.has(item.path)) {
14644
14735
  ctx.addIssue({
14645
14736
  code: z2.ZodIssueCode.custom,
@@ -16640,7 +16731,7 @@ var ModelRouter = class _ModelRouter {
16640
16731
  value
16641
16732
  );
16642
16733
  }
16643
- if (arguments.length === 1 && isPlainObject28(keyOrOptions)) {
16734
+ if (arguments.length === 1 && isPlainObject29(keyOrOptions)) {
16644
16735
  assertMutableRouterOptions("model", keyOrOptions);
16645
16736
  this.runtime.setModelOptions(
16646
16737
  this.modelName,
@@ -17174,7 +17265,7 @@ var RootRouter = class {
17174
17265
 
17175
17266
  // src/routers/data-router.ts
17176
17267
  import JsonRouter10 from "@web-ts-toolkit/express-json-router";
17177
- import { isPlainObject as isPlainObject29, isString as isString9, isUndefined as isUndefined3, normalizeUrlPath as normalizeUrlPath5 } from "@web-ts-toolkit/utils";
17268
+ import { isPlainObject as isPlainObject30, isString as isString9, isUndefined as isUndefined3, normalizeUrlPath as normalizeUrlPath5 } from "@web-ts-toolkit/utils";
17178
17269
  init_runtime();
17179
17270
  init_helpers();
17180
17271
  init_schemas();
@@ -17390,7 +17481,7 @@ var DataRouter = class {
17390
17481
  value
17391
17482
  );
17392
17483
  }
17393
- if (arguments.length === 1 && isPlainObject29(keyOrOptions)) {
17484
+ if (arguments.length === 1 && isPlainObject30(keyOrOptions)) {
17394
17485
  assertMutableRouterOptions("data", keyOrOptions);
17395
17486
  this.runtime.setDataOptions(this.dataName, keyOrOptions);
17396
17487
  }
@@ -17549,7 +17640,7 @@ function createRuntimeApi(runtime) {
17549
17640
  if (arguments.length === 2 && isString10(keyOrOptions)) {
17550
17641
  return runtime.setGlobalOption(keyOrOptions, value);
17551
17642
  }
17552
- if (arguments.length === 1 && isPlainObject30(keyOrOptions)) {
17643
+ if (arguments.length === 1 && isPlainObject31(keyOrOptions)) {
17553
17644
  return runtime.setGlobalOptions(keyOrOptions);
17554
17645
  }
17555
17646
  };