@web-ts-toolkit/access-router 0.56.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))
@@ -2232,7 +2256,8 @@ function normalizeSelection(select, catalog, label) {
2232
2256
  };
2233
2257
  }
2234
2258
  function captureVirtualPopulateDependencies(runtime, modelName, options, globalOptions = runtime.getGlobalOptions()) {
2235
- const requireReadOperationAccessForPopulateDependencies = globalOptions.requireReadOperationAccessForPopulateDependencies !== false;
2259
+ const requireOperationAccessForPopulateDependencies = globalOptions.requireOperationAccessForPopulateDependencies !== false;
2260
+ const requireOperationAccessForPopulateQueries = globalOptions.requireOperationAccessForPopulateQueries !== false;
2236
2261
  const model = runtime.getModelInstance(modelName);
2237
2262
  const targets = /* @__PURE__ */ new Map();
2238
2263
  const scopes = [];
@@ -2272,7 +2297,12 @@ function captureVirtualPopulateDependencies(runtime, modelName, options, globalO
2272
2297
  label,
2273
2298
  `target "${ref}" requires a registered same-connection non-discriminator ObjectId model bound to the source ref`
2274
2299
  );
2275
- 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
+ );
2276
2306
  const defaults = runtime.getDefaultModelOptions();
2277
2307
  const readOption = (key) => resolveNestedOption((path2, fallback) => get4(targetOptions, path2, fallback), key, get4(defaults, key));
2278
2308
  const documentPermissionField = targetOptions.documentPermissionField ?? defaults.documentPermissionField;
@@ -2281,7 +2311,9 @@ function captureVirtualPopulateDependencies(runtime, modelName, options, globalO
2281
2311
  {
2282
2312
  catalog,
2283
2313
  documentPermissionField,
2284
- requireReadOperationAccessForPopulateDependencies,
2314
+ requireOperationAccessForPopulateDependencies,
2315
+ requireOperationAccessForPopulateQueries,
2316
+ permissionSchema: targetOptions.permissionSchema,
2285
2317
  operationAccess: { read: readOption("operationAccess.read") },
2286
2318
  baseFilter: { read: readOption("baseFilter.read") },
2287
2319
  overrideFilter: { read: readOption("overrideFilter.read") },
@@ -2330,13 +2362,13 @@ function captureVirtualPopulateDependencies(runtime, modelName, options, globalO
2330
2362
  );
2331
2363
  continue;
2332
2364
  }
2333
- if (!isPlainObject12(raw)) continue;
2365
+ if (!isPlainObject13(raw)) continue;
2334
2366
  const entry = raw;
2335
2367
  const label = join(sourcePrefix, name);
2336
2368
  captureList(entry.dependsOnPopulate, label);
2337
2369
  if (!("get" in entry))
2338
2370
  for (const access of accesses) {
2339
- if (isPlainObject12(entry[access]))
2371
+ if (isPlainObject13(entry[access]))
2340
2372
  captureList(entry[access].dependsOnPopulate, `${label}.${access}`);
2341
2373
  }
2342
2374
  const byAccess = {};
@@ -2394,6 +2426,7 @@ var init_virtual_populate_dependencies = __esm({
2394
2426
  init_authorization_snapshot();
2395
2427
  init_field_catalog();
2396
2428
  init_virtual_descriptor();
2429
+ init_populate_target_configuration();
2397
2430
  elementType = (type) => type.$embeddedSchemaType ?? type.embeddedSchemaType ?? type.caster;
2398
2431
  join = (prefix, path) => prefix ? `${prefix}.${path}` : path;
2399
2432
  within = (path, parent) => path === parent || path.startsWith(`${parent}.`);
@@ -2418,7 +2451,7 @@ import {
2418
2451
  isBoolean,
2419
2452
  isFunction as isFunction2,
2420
2453
  isNil as isNil2,
2421
- isPlainObject as isPlainObject13,
2454
+ isPlainObject as isPlainObject14,
2422
2455
  isString as isString4,
2423
2456
  keys,
2424
2457
  normalizeUrlPath,
@@ -2485,7 +2518,7 @@ var init_runtime = __esm({
2485
2518
  const modelPermissionKeys = {};
2486
2519
  forEach2(schemaKeys, (schemaKey) => {
2487
2520
  const schemaRule = permissionSchema[schemaKey];
2488
- 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]);
2489
2522
  for (let x = 0; x < ruleEntries.length; x++) {
2490
2523
  const [accessKey, value] = ruleEntries[x];
2491
2524
  if (!isArray5(globalPermissionKeys[accessKey])) {
@@ -2526,7 +2559,7 @@ var init_runtime = __esm({
2526
2559
  VIRTUAL_RESERVED_NAMES = /* @__PURE__ */ new Set(["_id", "__v", "id"]);
2527
2560
  VIRTUAL_DANGEROUS_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
2528
2561
  VIRTUAL_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_$]*$/;
2529
- isVirtualContainerValue2 = (value) => isPlainObject13(value) && "sub" in value;
2562
+ isVirtualContainerValue2 = (value) => isPlainObject14(value) && "sub" in value;
2530
2563
  virtualFullPath = (prefix, name) => prefix ? `${prefix}.${name}` : name;
2531
2564
  virtualPathsOverlap = (virtualPath, permissionField) => {
2532
2565
  if (!virtualPath || !permissionField) return false;
@@ -2621,7 +2654,7 @@ var init_runtime = __esm({
2621
2654
  };
2622
2655
  validateVirtualLeafValue = (value, entryLabel, storedFields, siblingVirtualNames, sharedDependsOn) => {
2623
2656
  if (isFunction2(value)) return;
2624
- if (!isPlainObject13(value)) {
2657
+ if (!isPlainObject14(value)) {
2625
2658
  throw new Error(`Virtual ${entryLabel}: descriptor must be a getter function or { get, dependsOn }`);
2626
2659
  }
2627
2660
  const record3 = value;
@@ -2667,7 +2700,7 @@ var init_runtime = __esm({
2667
2700
  `Virtual ${scopeLabel}${name}: scope-container must only hold "sub" (embedded container "${name}" is not a virtual-leaf collision)`
2668
2701
  );
2669
2702
  }
2670
- if (!isPlainObject13(record4.sub)) {
2703
+ if (!isPlainObject14(record4.sub)) {
2671
2704
  throw new Error(`Virtual ${scopeLabel}${name}: scope-container "sub" must be a plain object`);
2672
2705
  }
2673
2706
  continue;
@@ -2678,7 +2711,7 @@ var init_runtime = __esm({
2678
2711
  );
2679
2712
  }
2680
2713
  if (isFunction2(raw)) continue;
2681
- if (!isPlainObject13(raw)) {
2714
+ if (!isPlainObject14(raw)) {
2682
2715
  throw new Error(
2683
2716
  `Virtual ${scopeLabel}${name}: malformed descriptor (expected getter, { get, dependsOn }, or per-access record)`
2684
2717
  );
@@ -2732,7 +2765,7 @@ var init_runtime = __esm({
2732
2765
  };
2733
2766
  validateVirtualsTree = (model, virtuals, documentPermissionField, modelName) => {
2734
2767
  if (virtuals === void 0 || virtuals === null) return;
2735
- if (!isPlainObject13(virtuals)) {
2768
+ if (!isPlainObject14(virtuals)) {
2736
2769
  throw new Error(`Virtuals for model "${modelName}": virtuals must be a plain object mapping names to descriptors`);
2737
2770
  }
2738
2771
  const rootSchema = model?.schema ?? null;
@@ -2784,8 +2817,8 @@ var init_runtime = __esm({
2784
2817
  {
2785
2818
  requestPermissionField: "_permissions",
2786
2819
  globalPermissions: () => ({}),
2787
- requireRegisteredPopulateModels: true,
2788
- requireReadOperationAccessForPopulateDependencies: true,
2820
+ requireOperationAccessForPopulateQueries: true,
2821
+ requireOperationAccessForPopulateDependencies: true,
2789
2822
  logger: defaultLogger,
2790
2823
  requestComplexity: defaultRequestComplexity
2791
2824
  },
@@ -2959,13 +2992,15 @@ var init_runtime = __esm({
2959
2992
  /** Shallowly replace supplied global values; nested objects such as requestComplexity are not deep-merged. */
2960
2993
  setGlobalOptions(options) {
2961
2994
  const next = { ...options };
2962
- if ("requireReadOperationAccessForPopulateDependencies" in next)
2963
- next.requireReadOperationAccessForPopulateDependencies = next.requireReadOperationAccessForPopulateDependencies !== false;
2995
+ if ("requireOperationAccessForPopulateQueries" in next)
2996
+ next.requireOperationAccessForPopulateQueries = next.requireOperationAccessForPopulateQueries !== false;
2997
+ if ("requireOperationAccessForPopulateDependencies" in next)
2998
+ next.requireOperationAccessForPopulateDependencies = next.requireOperationAccessForPopulateDependencies !== false;
2964
2999
  this.globalOptions.assign(next);
2965
3000
  }
2966
3001
  setGlobalOption(key, value) {
2967
- if (key === "requireReadOperationAccessForPopulateDependencies") {
2968
- this.globalOptions.set("requireReadOperationAccessForPopulateDependencies", value !== false);
3002
+ if (key === "requireOperationAccessForPopulateQueries" || key === "requireOperationAccessForPopulateDependencies") {
3003
+ this.globalOptions.set(key, value !== false);
2969
3004
  return;
2970
3005
  }
2971
3006
  this.globalOptions.set(key, value);
@@ -3088,10 +3123,10 @@ var init_runtime = __esm({
3088
3123
  const segments = nestedPath.split(".");
3089
3124
  let cursor = cloned;
3090
3125
  for (let i = 0; i < segments.length - 1; i++) {
3091
- if (!isPlainObject13(cursor)) break;
3126
+ if (!isPlainObject14(cursor)) break;
3092
3127
  cursor = cursor[segments[i]];
3093
3128
  }
3094
- if (isPlainObject13(cursor)) {
3129
+ if (isPlainObject14(cursor)) {
3095
3130
  delete cursor[segments[segments.length - 1]];
3096
3131
  }
3097
3132
  proposedVirtuals = cloned;
@@ -3583,7 +3618,6 @@ function normalizePolicy(value, applicability, configuration, targets) {
3583
3618
  const matches = targets.filter((target2) => target2.path === path);
3584
3619
  if (matches.length !== 1) authorizationError(`unused or ambiguous related path: ${path}`);
3585
3620
  const target = matches[0];
3586
- if (target.kind === "passthrough") authorizationError(`related policy requires a registered target: ${path}`);
3587
3621
  const child = authorizationRecord(entry, `related.${path}`);
3588
3622
  assertAuthorizationKeys(
3589
3623
  child,
@@ -4093,7 +4127,7 @@ var init_field_authorization = __esm({
4093
4127
  });
4094
4128
 
4095
4129
  // src/acl/virtual-projection.ts
4096
- 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";
4097
4131
  function planVirtualProjection(args) {
4098
4132
  if (args.snapshot.catalog) return planCatalogProjection(args);
4099
4133
  const {
@@ -4327,15 +4361,15 @@ var init_virtual_projection = __esm({
4327
4361
  init_virtual_populate_dependencies();
4328
4362
  init_virtual_descriptor();
4329
4363
  getScopedPermissionSchema = (root, scopePath) => {
4330
- if (!root || !isPlainObject15(root)) return null;
4364
+ if (!root || !isPlainObject16(root)) return null;
4331
4365
  if (scopePath.length === 0) return root;
4332
4366
  const scoped = get6(root, scopePath.join("."));
4333
- return isPlainObject15(scoped) ? scoped : null;
4367
+ return isPlainObject16(scoped) ? scoped : null;
4334
4368
  };
4335
4369
  resolveAlwaysSelectForAccess = (raw, access) => {
4336
4370
  if (!raw) return [];
4337
4371
  if (isArray7(raw)) return [...raw];
4338
- if (isPlainObject15(raw)) {
4372
+ if (isPlainObject16(raw)) {
4339
4373
  const record3 = raw;
4340
4374
  const exact = record3[access];
4341
4375
  if (isArray7(exact)) return [...exact];
@@ -4622,9 +4656,9 @@ function captureModelAuthorizationConfiguration(runtime, modelName, options = ru
4622
4656
  options,
4623
4657
  globalOptions
4624
4658
  ),
4625
- requireReadOperationAccessForPopulateDependencies: globalOptions.requireReadOperationAccessForPopulateDependencies !== false,
4626
- requireRegisteredPopulateModels: globalOptions.requireRegisteredPopulateModels,
4627
- permissionSchema: options.permissionSchema,
4659
+ requireOperationAccessForPopulateDependencies: globalOptions.requireOperationAccessForPopulateDependencies !== false,
4660
+ requireOperationAccessForPopulateQueries: globalOptions.requireOperationAccessForPopulateQueries !== false,
4661
+ permissionSchema: options.permissionSchema === void 0 ? runtime.getDefaultModelOption("permissionSchema") : options.permissionSchema,
4628
4662
  virtuals,
4629
4663
  alwaysSelectFields: options.alwaysSelectFields,
4630
4664
  modelPermissionPrefix: options.modelPermissionPrefix,
@@ -4690,15 +4724,15 @@ import {
4690
4724
  isBoolean as isBoolean2,
4691
4725
  isEmpty as isEmpty2,
4692
4726
  isFunction as isFunction3,
4693
- isPlainObject as isPlainObject16,
4727
+ isPlainObject as isPlainObject17,
4694
4728
  isString as isString5
4695
4729
  } from "@web-ts-toolkit/utils";
4696
4730
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
4697
4731
  function isAndFilter(filter2) {
4698
- 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);
4699
4733
  }
4700
4734
  function isMergeableClause(filter2) {
4701
- return !!filter2 && isPlainObject16(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
4735
+ return !!filter2 && isPlainObject17(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
4702
4736
  }
4703
4737
  function optimizeAndFilter(clausesInput) {
4704
4738
  const clauses = [];
@@ -4719,7 +4753,7 @@ function optimizeAndFilter(clausesInput) {
4719
4753
  const entries = Array.from(value, dedupeKey);
4720
4754
  return entries.every((entry) => entry !== void 0) ? `array:${JSON.stringify(entries)}` : void 0;
4721
4755
  }
4722
- if (isPlainObject16(value)) {
4756
+ if (isPlainObject17(value)) {
4723
4757
  const entries = Object.keys(value).sort().map((key) => [key, dedupeKey(value[key])]);
4724
4758
  return entries.every(([, entry]) => entry !== void 0) ? `object:${JSON.stringify(entries)}` : void 0;
4725
4759
  }
@@ -4767,7 +4801,7 @@ function optimizeAndFilter(clausesInput) {
4767
4801
  }
4768
4802
  function normalizeFilter(filter2) {
4769
4803
  if (filter2 === false) return false;
4770
- if (!filter2 || !isPlainObject16(filter2) || isEmpty2(filter2)) return null;
4804
+ if (!filter2 || !isPlainObject17(filter2) || isEmpty2(filter2)) return null;
4771
4805
  if (!isAndFilter(filter2)) {
4772
4806
  return filter2;
4773
4807
  }
@@ -4845,7 +4879,7 @@ async function setRequestPermissions(req) {
4845
4879
  if (hasFieldValue && owner === void 0) return;
4846
4880
  const globalPermissions = getGlobalOption("globalPermissions");
4847
4881
  const permissions = isFunction3(globalPermissions) ? await globalPermissions.call(req, req) : void 0;
4848
- if (isPlainObject16(permissions)) req[requestPermissionField] = permissions;
4882
+ if (isPlainObject17(permissions)) req[requestPermissionField] = permissions;
4849
4883
  else if (isArray8(permissions)) req[requestPermissionField] = arrayToRecord(permissions);
4850
4884
  else if (isString5(permissions)) req[requestPermissionField] = { [permissions]: true };
4851
4885
  else req[requestPermissionField] = {};
@@ -5244,7 +5278,7 @@ var init_filter_resolution = __esm({
5244
5278
  // src/acl/virtual-populate-execution.ts
5245
5279
  import { Document as Document4, mongo as mongo4 } from "mongoose";
5246
5280
  import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
5247
- import { isPlainObject as isPlainObject19 } from "@web-ts-toolkit/utils";
5281
+ import { isPlainObject as isPlainObject20 } from "@web-ts-toolkit/utils";
5248
5282
  function projectVirtualPopulateView(source, selection, structurePaths = []) {
5249
5283
  const root = { children: /* @__PURE__ */ new Map() };
5250
5284
  for (const path of compactPersistedProjection(selection)) {
@@ -5276,8 +5310,8 @@ function equivalentVirtualPopulatePredicate(left, right) {
5276
5310
  }
5277
5311
  if (Array.isArray(left) || Array.isArray(right))
5278
5312
  return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((entry, index) => equivalentVirtualPopulatePredicate(entry, right[index]));
5279
- if (isPlainObject19(left) || isPlainObject19(right)) {
5280
- if (!isPlainObject19(left) || !isPlainObject19(right)) return false;
5313
+ if (isPlainObject20(left) || isPlainObject20(right)) {
5314
+ if (!isPlainObject20(left) || !isPlainObject20(right)) return false;
5281
5315
  const a = Object.keys(left), b = Object.keys(right);
5282
5316
  return a.length === b.length && a.every(
5283
5317
  (key, index) => key === b[index] && equivalentVirtualPopulatePredicate(
@@ -5348,7 +5382,6 @@ function captureVirtualPopulateRows(batch, inputs, plan, publicPopulate = [], so
5348
5382
  address,
5349
5383
  plan: currentPlan,
5350
5384
  refs: /* @__PURE__ */ new Map(),
5351
- referenceTargets: /* @__PURE__ */ new Map(),
5352
5385
  dependencies: /* @__PURE__ */ new Map()
5353
5386
  };
5354
5387
  row.occurrences.set(addressKey(address), occurrence);
@@ -5356,7 +5389,6 @@ function captureVirtualPopulateRows(batch, inputs, plan, publicPopulate = [], so
5356
5389
  const dependencies = (candidate.populateDependencies ?? []).map((dependency) => {
5357
5390
  if (!occurrence.refs.has(dependency.path))
5358
5391
  occurrence.refs.set(dependency.path, originalRef(value, dependency.path));
5359
- occurrence.referenceTargets.set(dependency.path, dependency.target);
5360
5392
  const request = {
5361
5393
  binding: dependency.target,
5362
5394
  target: dependency.target,
@@ -5454,7 +5486,7 @@ async function execute(state) {
5454
5486
  if (!pending) {
5455
5487
  pending = (async () => {
5456
5488
  try {
5457
- if (target.configuration.requireReadOperationAccessForPopulateDependencies !== false && !await targetReadAllowed(state, target))
5489
+ if (target.configuration.requireOperationAccessForPopulateDependencies !== false && !await targetReadAllowed(state, target))
5458
5490
  return void 0;
5459
5491
  const parts = await work.hookGate.run(
5460
5492
  () => resolveAccessFilterParts({
@@ -5540,12 +5572,6 @@ function readVirtualPopulateOriginalReference(row, address, path) {
5540
5572
  function hasVirtualPopulatePublicReference(row, address, path) {
5541
5573
  return !!rows.get(row)?.publicRequests.some((request) => addressKey(request.address) === addressKey([...address, path]));
5542
5574
  }
5543
- function isVirtualPopulateReferenceVisible(row, address, path) {
5544
- const record3 = rows.get(row);
5545
- const occurrence = record3?.occurrences.get(addressKey(address));
5546
- const target = occurrence?.refs.get(path)?.present ? occurrence.referenceTargets.get(path) : void 0;
5547
- return record3 && target ? targetReadAllowed(record3.batch, target) : Promise.resolve(true);
5548
- }
5549
5575
  function readVirtualPopulateInputs(row, address, virtualName) {
5550
5576
  const record3 = rows.get(row);
5551
5577
  if (!record3?.batch.completed) return { available: false };
@@ -5688,7 +5714,7 @@ var init_virtual_populate_execution = __esm({
5688
5714
  publicViewIdentities = /* @__PURE__ */ new WeakMap();
5689
5715
  familyRawFetches = /* @__PURE__ */ new WeakMap();
5690
5716
  addressKey = (address) => JSON.stringify(address);
5691
- 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;
5692
5718
  originalRef = (source, key) => {
5693
5719
  const record3 = storage(source);
5694
5720
  return {
@@ -5701,7 +5727,7 @@ var init_virtual_populate_execution = __esm({
5701
5727
  const option = getCapturedAuthorizationOption(configuration, `${key}.${access}`);
5702
5728
  return key === "operationAccess" || typeof option === "function" ? option : void 0;
5703
5729
  });
5704
- 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.requireReadOperationAccessForPopulateDependencies === b.binding.configuration.requireReadOperationAccessForPopulateDependencies && // 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.
5705
5731
  equivalentVirtualPopulatePredicate(
5706
5732
  a.binding.configuration.catalog.persistedPaths,
5707
5733
  b.binding.configuration.catalog.persistedPaths
@@ -5715,7 +5741,7 @@ var init_virtual_populate_execution = __esm({
5715
5741
  });
5716
5742
 
5717
5743
  // src/acl/output-field-filter.ts
5718
- import { isPlainObject as isPlainObject20 } from "@web-ts-toolkit/utils";
5744
+ import { isPlainObject as isPlainObject21 } from "@web-ts-toolkit/utils";
5719
5745
  async function resolveOutputDocumentAuthorization(args) {
5720
5746
  const { authorization, binding } = args;
5721
5747
  const { configuration, access } = readServiceAuthorization(authorization, binding);
@@ -5729,7 +5755,7 @@ async function resolveOutputDocumentAuthorization(args) {
5729
5755
  try {
5730
5756
  const run = () => hook.call(this, doc, permissions, args.context);
5731
5757
  const grants = await getRequestWorkState(binding.request, binding.runtime).hookGate.run(run);
5732
- return isPlainObject20(grants) ? grants : {};
5758
+ return isPlainObject21(grants) ? grants : {};
5733
5759
  } catch (error2) {
5734
5760
  warn("docPermissions hook failed; applying empty document permissions", {
5735
5761
  modelName: binding.modelName,
@@ -5844,7 +5870,7 @@ function copyOutputValue(value, plan, path) {
5844
5870
  const visit = (input, current) => {
5845
5871
  if (Array.isArray(input)) return input.map((entry) => visit(entry, current));
5846
5872
  if (input instanceof Map) return visit(Object.fromEntries(input), current);
5847
- if (!isPlainObject20(input)) return deepIsolateValue(input);
5873
+ if (!isPlainObject21(input)) return deepIsolateValue(input);
5848
5874
  const output = {};
5849
5875
  for (const [key, entry] of Object.entries(input)) {
5850
5876
  const child = `${current}.${key}`;
@@ -5860,25 +5886,21 @@ function copyOutputValue(value, plan, path) {
5860
5886
  }
5861
5887
  function copyAuthorizedReference(value, plan, path) {
5862
5888
  if (Array.isArray(value)) return value.map((entry) => copyAuthorizedReference(entry, plan, path));
5863
- if (!isPlainObject20(value)) return deepIsolateValue(value);
5889
+ if (!isPlainObject21(value)) return deepIsolateValue(value);
5864
5890
  const handle = getAssociatedDocumentAuthorization(value, plan.authorization);
5865
5891
  if (handle) {
5866
5892
  const copy = deepIsolateValue(value);
5867
5893
  transferDocumentAuthorizationGraph(value, copy);
5868
5894
  return copy;
5869
5895
  }
5870
- const { relatedTargets } = readServiceAuthorization(plan.authorization, plan.authorizationBinding);
5871
- if (relatedTargets.some((target) => target.path === path && target.kind === "passthrough")) {
5872
- return deepIsolateValue(value);
5873
- }
5874
5896
  return Object.prototype.hasOwnProperty.call(value, "_id") ? deepIsolateValue(value._id) : void 0;
5875
5897
  }
5876
5898
  function retainOutputMetadata(output, source, configuration, scope2) {
5877
5899
  if (scope2.length || configuration.stripPermissionsField) return;
5878
5900
  const parts = (configuration.documentPermissionField ?? "_permissions").split(".");
5879
5901
  let value = source;
5880
- for (const part of parts) value = isPlainObject20(value) ? value[part] : void 0;
5881
- if (!isPlainObject20(value)) return;
5902
+ for (const part of parts) value = isPlainObject21(value) ? value[part] : void 0;
5903
+ if (!isPlainObject21(value)) return;
5882
5904
  const keys2 = configuration.exposedDocPermissionKeys;
5883
5905
  const metadata = Object.fromEntries(
5884
5906
  Object.entries(value).filter(
@@ -5888,7 +5910,7 @@ function retainOutputMetadata(output, source, configuration, scope2) {
5888
5910
  const copy = { ...output };
5889
5911
  let target = copy;
5890
5912
  for (const part of parts.slice(0, -1)) {
5891
- target[part] = isPlainObject20(target[part]) ? { ...target[part] } : {};
5913
+ target[part] = isPlainObject21(target[part]) ? { ...target[part] } : {};
5892
5914
  target = target[part];
5893
5915
  }
5894
5916
  target[parts[parts.length - 1]] = deepIsolateValue(metadata);
@@ -5930,7 +5952,7 @@ async function filterExistingOutput(source, plan, handle, associated) {
5930
5952
  for (const entry of value2) values.push(await one(entry) ?? null);
5931
5953
  return values;
5932
5954
  }
5933
- if (!isPlainObject20(value2)) return value2 === null ? null : void 0;
5955
+ if (!isPlainObject21(value2)) return value2 === null ? null : void 0;
5934
5956
  const childHandle = associated ? getAssociatedDocumentAuthorization(value2) : void 0;
5935
5957
  let child = scoped;
5936
5958
  if (childHandle) {
@@ -6010,7 +6032,7 @@ async function filterModelOutputForHelper(args) {
6010
6032
  const plan = helperPlan(normalized.authorization, binding);
6011
6033
  if (args.doc == null) return { output: args.doc, fields: [] };
6012
6034
  const source = isolateDocForOutput(args.doc);
6013
- 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");
6014
6036
  const knownOutput = !!args.doc && typeof args.doc === "object" && isKnownAuthorizationOutput(args.doc);
6015
6037
  const handle = await resolveOutputDocumentAuthorization({
6016
6038
  authorization: normalized.authorization,
@@ -6105,7 +6127,7 @@ __export(finalize_model_output_exports, {
6105
6127
  finalizeModelOutput: () => finalizeModelOutput,
6106
6128
  finalizeModelOutputs: () => finalizeModelOutputs
6107
6129
  });
6108
- 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";
6109
6131
  async function finalizeSingleDoc(args, input) {
6110
6132
  const scopePath = args.scopePath;
6111
6133
  const plan = args.plan;
@@ -6121,8 +6143,8 @@ async function finalizeSingleDoc(args, input) {
6121
6143
  const privateRow = args.virtualPopulateRow ?? (original && typeof original === "object" ? getVirtualPopulateRow(original) : void 0);
6122
6144
  const privateAddress = args.virtualPopulateAddress ?? [];
6123
6145
  const isolated = isolateDocForOutput(input);
6124
- const output = isPlainObject21(isolated) ? isolated : {};
6125
- if (captured && isPlainObject21(input))
6146
+ const output = isPlainObject22(isolated) ? isolated : {};
6147
+ if (captured && isPlainObject22(input))
6126
6148
  for (const key of Object.keys(output)) {
6127
6149
  transferDocumentAuthorizationGraph(input[key], output[key]);
6128
6150
  }
@@ -6177,7 +6199,7 @@ async function finalizeSingleDoc(args, input) {
6177
6199
  );
6178
6200
  continue;
6179
6201
  }
6180
- if (captured && !isPlainObject21(rawContainer) && !Array.isArray(rawContainer)) {
6202
+ if (captured && !isPlainObject22(rawContainer) && !Array.isArray(rawContainer)) {
6181
6203
  delete output[containerName];
6182
6204
  continue;
6183
6205
  }
@@ -6201,7 +6223,7 @@ async function finalizeSingleDoc(args, input) {
6201
6223
  const finalizeOneChild = async (rawChild, address) => {
6202
6224
  if (captured && Array.isArray(rawChild))
6203
6225
  return mapWithConcurrencyLimit(rawChild, limit, (entry, index) => finalizeOneChild(entry, [...address, index]));
6204
- if (!isPlainObject21(rawChild) && !Array.isArray(rawChild)) return captured ? null : deepIsolateValue(rawChild);
6226
+ if (!isPlainObject22(rawChild) && !Array.isArray(rawChild)) return captured ? null : deepIsolateValue(rawChild);
6205
6227
  if (args.traversal?.finalizeEmbeddedChildren) {
6206
6228
  return args.traversal.finalizeEmbeddedChildren({
6207
6229
  containerName,
@@ -6227,7 +6249,7 @@ async function finalizeSingleDoc(args, input) {
6227
6249
  (entry, index) => finalizeOneChild(entry, [...privateAddress, containerName, index])
6228
6250
  );
6229
6251
  output[containerName] = finalized;
6230
- } else if (isPlainObject21(rawContainer)) {
6252
+ } else if (isPlainObject22(rawContainer)) {
6231
6253
  output[containerName] = await finalizeOneChild(rawContainer, [
6232
6254
  ...privateAddress,
6233
6255
  containerName
@@ -6271,7 +6293,7 @@ async function finalizeSingleDoc(args, input) {
6271
6293
  related: target.plan.relatedPlans?.length ? (args.related ?? []).filter((entry) => entry.path.startsWith(`${target.path}.`)) : []
6272
6294
  };
6273
6295
  const finalizeOneRelated = async (rawChild, originalChild = rawChild) => {
6274
- if (!isPlainObject21(rawChild) && !Array.isArray(rawChild)) return rawChild;
6296
+ if (!isPlainObject22(rawChild) && !Array.isArray(rawChild)) return rawChild;
6275
6297
  if (typeof rawChild?._bsontype === "string") return rawChild;
6276
6298
  const childOriginal = getIncludeOriginal(originalChild);
6277
6299
  const childRow = childOriginal && typeof childOriginal === "object" ? getVirtualPopulateRow(childOriginal) : void 0;
@@ -6292,7 +6314,7 @@ async function finalizeSingleDoc(args, input) {
6292
6314
  limit,
6293
6315
  (entry, index) => finalizeOneRelated(entry, Array.isArray(originalRelated) ? originalRelated[index] : void 0)
6294
6316
  );
6295
- } else if (isPlainObject21(rawRelated)) {
6317
+ } else if (isPlainObject22(rawRelated)) {
6296
6318
  finalized = await finalizeOneRelated(rawRelated, originalRelated);
6297
6319
  } else {
6298
6320
  continue;
@@ -6412,7 +6434,6 @@ async function finalizeSingleDoc(args, input) {
6412
6434
  if (!captured && documentPermissionField in output) keep.add(documentPermissionField);
6413
6435
  const trimmed = {};
6414
6436
  for (const key of keep) {
6415
- if (privateRow && !await isVirtualPopulateReferenceVisible(privateRow, privateAddress, key)) continue;
6416
6437
  if (privateRow && !hasVirtualPopulatePublicReference(privateRow, privateAddress, key)) {
6417
6438
  const ref = readVirtualPopulateOriginalReference(privateRow, privateAddress, key);
6418
6439
  if (ref) {
@@ -6494,20 +6515,20 @@ var init_finalize_model_output = __esm({
6494
6515
  init_field_catalog();
6495
6516
  init_virtual_populate_execution();
6496
6517
  init_include_output();
6497
- isVirtualContainerValue3 = (value) => isPlainObject21(value) && "sub" in value;
6518
+ isVirtualContainerValue3 = (value) => isPlainObject22(value) && "sub" in value;
6498
6519
  getScopeVirtualsObject2 = (rootVirtuals, scopePath) => {
6499
- if (!rootVirtuals || !isPlainObject21(rootVirtuals)) return null;
6520
+ if (!rootVirtuals || !isPlainObject22(rootVirtuals)) return null;
6500
6521
  if (scopePath.length === 0) return rootVirtuals;
6501
6522
  let current = rootVirtuals;
6502
6523
  for (let i = 0; i < scopePath.length; i += 2) {
6503
6524
  const field = scopePath[i];
6504
6525
  const subMarker = scopePath[i + 1];
6505
6526
  if (!field || subMarker !== "sub") return null;
6506
- if (!isPlainObject21(current)) return null;
6527
+ if (!isPlainObject22(current)) return null;
6507
6528
  const container = current[field];
6508
6529
  if (!isVirtualContainerValue3(container)) return null;
6509
6530
  current = container.sub;
6510
- if (!isPlainObject21(current)) return null;
6531
+ if (!isPlainObject22(current)) return null;
6511
6532
  }
6512
6533
  return current;
6513
6534
  };
@@ -6520,11 +6541,11 @@ var init_finalize_model_output = __esm({
6520
6541
  return Object.entries(scopeVirtuals).filter(([, v]) => v !== void 0 && !isVirtualContainerValue3(v)).map(([k]) => k);
6521
6542
  };
6522
6543
  getScopedPermissionSchema2 = (root, scopePath) => {
6523
- if (!root || !isPlainObject21(root)) return null;
6544
+ if (!root || !isPlainObject22(root)) return null;
6524
6545
  if (scopePath.length === 0) return root;
6525
6546
  try {
6526
6547
  const scoped = get9(root, scopePath.join("."));
6527
- return isPlainObject21(scoped) ? scoped : null;
6548
+ return isPlainObject22(scoped) ? scoped : null;
6528
6549
  } catch {
6529
6550
  return null;
6530
6551
  }
@@ -6583,7 +6604,7 @@ var init_finalize_model_output = __esm({
6583
6604
  const grants = await args.sharedGate.run(
6584
6605
  () => hook.call(args.request, isolatedForHook, globalPerms, scopeContext)
6585
6606
  );
6586
- const normalized = isPlainObject21(grants) ? grants : {};
6607
+ const normalized = isPlainObject22(grants) ? grants : {};
6587
6608
  scopeContext.docPermissions = normalized;
6588
6609
  return normalized;
6589
6610
  }
@@ -6605,19 +6626,21 @@ var init_finalize_model_output = __esm({
6605
6626
  });
6606
6627
 
6607
6628
  // src/index.ts
6608
- 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";
6609
6630
 
6610
6631
  // src/middleware.ts
6611
6632
  import JsonRouter5 from "@web-ts-toolkit/express-json-router";
6612
- 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";
6613
6634
 
6614
6635
  // src/core.ts
6615
6636
  init_public_admission();
6616
6637
  init_options();
6638
+ init_permission();
6617
6639
  import {
6618
6640
  castArray as castArray4,
6619
6641
  compact as compact3,
6620
6642
  forEach as forEach6,
6643
+ get as get15,
6621
6644
  isArray as isArray12,
6622
6645
  isBoolean as isBoolean5,
6623
6646
  isFunction as isFunction6,
@@ -6627,27 +6650,6 @@ import {
6627
6650
  uniq as uniq3
6628
6651
  } from "@web-ts-toolkit/utils";
6629
6652
 
6630
- // src/meta.ts
6631
- init_runtime();
6632
- init_runtime_context();
6633
- var getRuntime5 = () => getActiveRuntime() ?? defaultRuntime;
6634
- var getModelRef = (modelName, refPath) => {
6635
- const direct = getRuntime5().getModelRef(modelName, refPath);
6636
- if (direct) return direct;
6637
- if (!refPath.includes(".")) return null;
6638
- try {
6639
- const runtime = getRuntime5();
6640
- const instance = runtime.getModelInstance?.(
6641
- modelName
6642
- );
6643
- const schemaPath = instance?.schema?.path?.(refPath);
6644
- const ref = schemaPath?.options?.ref ?? schemaPath?.caster?.options?.ref ?? null;
6645
- return typeof ref === "string" && ref.length > 0 ? ref : null;
6646
- } catch {
6647
- return null;
6648
- }
6649
- };
6650
-
6651
6653
  // src/services/base.ts
6652
6654
  init_options();
6653
6655
  init_helpers();
@@ -6663,7 +6665,7 @@ import {
6663
6665
  forEach as forEach3,
6664
6666
  get as get8,
6665
6667
  isArray as isArray9,
6666
- isPlainObject as isPlainObject17,
6668
+ isPlainObject as isPlainObject18,
6667
6669
  set as set6,
6668
6670
  uniq
6669
6671
  } from "@web-ts-toolkit/utils";
@@ -6788,7 +6790,7 @@ init_public_admission();
6788
6790
  // src/correlated-includes.ts
6789
6791
  init_request_complexity();
6790
6792
  init_sort_policy();
6791
- 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";
6792
6794
  var PARENT_MARKER_KEY = "$parent";
6793
6795
  var ESCAPE_WRAPPER_KEY = "$escape";
6794
6796
  var SUBQUERY_PAYLOAD_KEYS = /* @__PURE__ */ new Set(["$$sq", "$$date"]);
@@ -6799,10 +6801,10 @@ var FIELD_SEGMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_$]*$/;
6799
6801
  var ARRAY_INDEX_SEGMENT_PATTERN = /^(0|[1-9]\d*)$/;
6800
6802
  var ownKeys = (value) => Object.keys(value);
6801
6803
  function isParentRefMarker(value) {
6802
- 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;
6803
6805
  }
6804
6806
  function isEscapedParentRef(value) {
6805
- 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;
6806
6808
  }
6807
6809
  function hasMarkerKey(value) {
6808
6810
  return ownKeys(value).includes(PARENT_MARKER_KEY) || ownKeys(value).includes(ESCAPE_WRAPPER_KEY);
@@ -6826,7 +6828,7 @@ function containsParentMarkerShape(value) {
6826
6828
  seen.add(current);
6827
6829
  return current.some(visit);
6828
6830
  }
6829
- if (isPlainObject14(current)) {
6831
+ if (isPlainObject15(current)) {
6830
6832
  if (seen.has(current)) return false;
6831
6833
  seen.add(current);
6832
6834
  if (hasMarkerKey(current)) return true;
@@ -6856,7 +6858,7 @@ function scanTemplateValueNode(value, path, issues) {
6856
6858
  }
6857
6859
  return;
6858
6860
  }
6859
- if (isPlainObject14(value)) {
6861
+ if (isPlainObject15(value)) {
6860
6862
  if (hasMarkerKey(value)) {
6861
6863
  issues.push({
6862
6864
  message: 'Malformed $parent reference: objects carrying $parent must be exactly { "$parent": "<field>" }',
@@ -6921,7 +6923,7 @@ function scanTemplateObjectEntries(obj, basePath, issues) {
6921
6923
  });
6922
6924
  return;
6923
6925
  }
6924
- if (isPlainObject14(clause)) {
6926
+ if (isPlainObject15(clause)) {
6925
6927
  scanTemplateObjectEntries(clause, clausePath, issues);
6926
6928
  }
6927
6929
  });
@@ -6932,7 +6934,7 @@ function scanTemplateObjectEntries(obj, basePath, issues) {
6932
6934
  }
6933
6935
  function validateCorrelatedFilterTemplate(filter2) {
6934
6936
  const issues = [];
6935
- if (!isPlainObject14(filter2)) {
6937
+ if (!isPlainObject15(filter2)) {
6936
6938
  issues.push({ message: "Correlated include filter must be an object", path: [] });
6937
6939
  return issues;
6938
6940
  }
@@ -6981,7 +6983,7 @@ function collectFilterReferencePaths(filter2) {
6981
6983
  value.forEach(visitValue);
6982
6984
  return;
6983
6985
  }
6984
- if (isPlainObject14(value)) {
6986
+ if (isPlainObject15(value)) {
6985
6987
  if (seen.has(value)) return;
6986
6988
  seen.add(value);
6987
6989
  for (const [key, child] of Object.entries(value)) {
@@ -7027,7 +7029,7 @@ function getOwnPathValue(parent, path) {
7027
7029
  return current;
7028
7030
  }
7029
7031
  function cloneSubstitutedValue(value) {
7030
- if (isPlainObject14(value) || isArray6(value)) return cloneDeep(value);
7032
+ if (isPlainObject15(value) || isArray6(value)) return cloneDeep(value);
7031
7033
  return value;
7032
7034
  }
7033
7035
  function literalParentRefObject(path) {
@@ -7076,7 +7078,7 @@ function resolveObjectEntries(obj, parent) {
7076
7078
  if (isParentRefMarker(clause) || isEscapedParentRef(clause)) {
7077
7079
  throw new CorrelatedReferenceError(`Unsupported $parent reference as a direct ${key} clause`);
7078
7080
  }
7079
- if (!isPlainObject14(clause)) return cloneSubstitutedValue(clause);
7081
+ if (!isPlainObject15(clause)) return cloneSubstitutedValue(clause);
7080
7082
  return resolveObjectEntries(clause, parent);
7081
7083
  });
7082
7084
  continue;
@@ -7105,10 +7107,10 @@ function resolveValueNode(template, parent, bare) {
7105
7107
  }
7106
7108
  const value = getOwnPathValue(parent, path);
7107
7109
  if (value === void 0 || value === null) throw UNRESOLVABLE;
7108
- if (bare && isPlainObject14(value)) return { $eq: cloneDeep(value) };
7110
+ if (bare && isPlainObject15(value)) return { $eq: cloneDeep(value) };
7109
7111
  return cloneSubstitutedValue(value);
7110
7112
  }
7111
- if (isPlainObject14(template)) {
7113
+ if (isPlainObject15(template)) {
7112
7114
  if (hasMarkerKey(template)) {
7113
7115
  throw new CorrelatedReferenceError(
7114
7116
  'Malformed $parent reference: objects carrying $parent must be exactly { "$parent": "<field>" }'
@@ -7150,7 +7152,7 @@ function resolveCorrelatedIdTemplate(id, parent) {
7150
7152
  }
7151
7153
  function resolveCorrelatedFilterTemplate(filter2, parent) {
7152
7154
  try {
7153
- if (!isPlainObject14(filter2) || isParentRefMarker(filter2) || isEscapedParentRef(filter2)) {
7155
+ if (!isPlainObject15(filter2) || isParentRefMarker(filter2) || isEscapedParentRef(filter2)) {
7154
7156
  throw new CorrelatedReferenceError("Correlated include filter must be an object");
7155
7157
  }
7156
7158
  return { status: "resolved", filter: resolveObjectEntries(filter2, parent) };
@@ -7165,7 +7167,7 @@ function validateExpandedCorrelatedOperands(expanded, limits) {
7165
7167
  var MAX_NESTED_INCLUDE_DEPTH = 10;
7166
7168
  function validateCorrelatedArgsShape(args, op, depth) {
7167
7169
  if (args === void 0) return [];
7168
- if (!isPlainObject14(args)) return ["Correlated include args must be an object"];
7170
+ if (!isPlainObject15(args)) return ["Correlated include args must be an object"];
7169
7171
  const record3 = args;
7170
7172
  const allowed = op === "list" ? /* @__PURE__ */ new Set(["select", "sort", "skip", "limit", "page", "pageSize", "include"]) : /* @__PURE__ */ new Set(["select", "sort", "include"]);
7171
7173
  for (const key of Object.keys(record3)) {
@@ -7187,7 +7189,7 @@ function validateCorrelatedArgsShape(args, op, depth) {
7187
7189
  return [];
7188
7190
  }
7189
7191
  function validateCorrelatedIncludeShape(entry, depth = 0) {
7190
- if (!isPlainObject14(entry)) return ["Correlated include must be an object"];
7192
+ if (!isPlainObject15(entry)) return ["Correlated include must be an object"];
7191
7193
  const record3 = entry;
7192
7194
  if (record3.mode !== "correlated") return ['Correlated include mode must be "correlated"'];
7193
7195
  if (typeof record3.model !== "string" || record3.model.length === 0)
@@ -7200,7 +7202,7 @@ function validateCorrelatedIncludeShape(entry, depth = 0) {
7200
7202
  if ("localField" in record3 || "foreignField" in record3) {
7201
7203
  return ["Correlated includes must not carry localField or foreignField"];
7202
7204
  }
7203
- 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)) {
7204
7206
  return ["Correlated include options must be absent or empty"];
7205
7207
  }
7206
7208
  const hasId = record3.id !== void 0;
@@ -7222,7 +7224,7 @@ function validateCorrelatedIncludeShape(entry, depth = 0) {
7222
7224
  return validateCorrelatedArgsShape(record3.args, record3.op, depth);
7223
7225
  }
7224
7226
  function isCorrelatedInclude(value) {
7225
- return isPlainObject14(value) && value.mode === "correlated";
7227
+ return isPlainObject15(value) && value.mode === "correlated";
7226
7228
  }
7227
7229
 
7228
7230
  // src/services/base.ts
@@ -7446,9 +7448,31 @@ function castCountGroups(input, values) {
7446
7448
  // src/services/base.ts
7447
7449
  init_service_authorization();
7448
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
+
7449
7472
  // src/acl/related-execution.ts
7450
7473
  init_public_admission();
7451
7474
  init_model_output_planning();
7475
+ init_populate_target_configuration();
7452
7476
  init_authorization_snapshot();
7453
7477
 
7454
7478
  // src/acl/service-execution.ts
@@ -7743,16 +7767,15 @@ function captureRelatedTargets(runtime, modelName, args = {}, depth = 0, globalO
7743
7767
  for (const entry of entries(args.populate)) {
7744
7768
  const path = typeof entry === "string" ? entry : entry.path;
7745
7769
  if (typeof entry !== "string") assertPopulateAccess(entry.access);
7746
- const model = getModelRef(modelName, path);
7747
- if (model && runtime.hasModel(model))
7770
+ const model = getModelRef(modelName, path, runtime);
7771
+ if (model) {
7772
+ const target = capturePopulateTargetConfiguration(runtime, model, `${modelName}.${path}`);
7748
7773
  targets.push({
7749
7774
  path,
7750
7775
  modelName: model,
7751
- configuration: captureModelAuthorizationConfiguration(runtime, model, void 0, globalOptions),
7776
+ configuration: captureModelAuthorizationConfiguration(runtime, model, target.options, globalOptions),
7752
7777
  staticObjectIdReference: staticObjectIdReference(runtime, modelName, path, model)
7753
7778
  });
7754
- else if (model && !(globalOptions.requireRegisteredPopulateModels === void 0 ? true : globalOptions.requireRegisteredPopulateModels)) {
7755
- targets.push({ path, modelName: model, kind: "passthrough", configuration: { catalog: { paths: [] } } });
7756
7779
  }
7757
7780
  }
7758
7781
  for (const entry of entries(args.include)) {
@@ -7938,20 +7961,20 @@ var copyAuthorizedOutput = (value) => {
7938
7961
  transferDocumentAuthorizationGraph(value, copy);
7939
7962
  return copy;
7940
7963
  };
7941
- var isVirtualContainerValueForGuard = (value) => isPlainObject17(value) && "sub" in value;
7964
+ var isVirtualContainerValueForGuard = (value) => isPlainObject18(value) && "sub" in value;
7942
7965
  var getScopeVirtualsForGuard = (rootVirtuals, scopePath) => {
7943
- if (!rootVirtuals || !isPlainObject17(rootVirtuals)) return null;
7966
+ if (!rootVirtuals || !isPlainObject18(rootVirtuals)) return null;
7944
7967
  if (scopePath.length === 0) return rootVirtuals;
7945
7968
  let current = rootVirtuals;
7946
7969
  for (let i = 0; i < scopePath.length; i += 2) {
7947
7970
  const field = scopePath[i];
7948
7971
  const subMarker = scopePath[i + 1];
7949
7972
  if (!field || subMarker !== "sub") return null;
7950
- if (!isPlainObject17(current)) return null;
7973
+ if (!isPlainObject18(current)) return null;
7951
7974
  const container = current[field];
7952
7975
  if (!isVirtualContainerValueForGuard(container)) return null;
7953
7976
  current = container.sub;
7954
- if (!isPlainObject17(current)) return null;
7977
+ if (!isPlainObject18(current)) return null;
7955
7978
  }
7956
7979
  return current;
7957
7980
  };
@@ -8017,7 +8040,7 @@ var sanitizeSubScopeVirtualKeys = (data, snapshot, sub) => {
8017
8040
  for (const entry of value) stripScope(entry, currentScope);
8018
8041
  return;
8019
8042
  }
8020
- if (!isPlainObject17(value)) return;
8043
+ if (!isPlainObject18(value)) return;
8021
8044
  const record3 = value;
8022
8045
  const leaves = getScopeVirtualLeafNames(snapshot, currentScope);
8023
8046
  for (const leaf of leaves) {
@@ -8030,7 +8053,7 @@ var sanitizeSubScopeVirtualKeys = (data, snapshot, sub) => {
8030
8053
  for (const key of Object.keys(record3)) {
8031
8054
  if (leaves.has(key) || containers.has(key)) continue;
8032
8055
  const child = record3[key];
8033
- if (Array.isArray(child) || isPlainObject17(child)) {
8056
+ if (Array.isArray(child) || isPlainObject18(child)) {
8034
8057
  stripScope(child, currentScope);
8035
8058
  }
8036
8059
  }
@@ -8042,7 +8065,7 @@ var LOGICAL_FILTER_KEYS = /* @__PURE__ */ new Set(["$and", "$or", "$nor"]);
8042
8065
  var isOperatorKey = (key) => key.startsWith("$");
8043
8066
  var REMOVED_VIRTUAL_FILTER = /* @__PURE__ */ Symbol("removed virtual filter clause");
8044
8067
  var rewriteFilterObject = (value, visit) => {
8045
- if (!isPlainObject17(value)) return value;
8068
+ if (!isPlainObject18(value)) return value;
8046
8069
  let changed = false;
8047
8070
  const entries = [];
8048
8071
  for (const [key, child] of Object.entries(value)) {
@@ -8073,7 +8096,7 @@ var resolveVirtualFilterField = (path, scope2) => {
8073
8096
  if (!Object.hasOwn(scope2, segment) || scope2[segment] === void 0) return null;
8074
8097
  const definition = scope2[segment];
8075
8098
  if (!isVirtualContainerValueForGuard(definition)) return REMOVED_VIRTUAL_FILTER;
8076
- scope2 = isPlainObject17(definition.sub) ? definition.sub : null;
8099
+ scope2 = isPlainObject18(definition.sub) ? definition.sub : null;
8077
8100
  inContainer = true;
8078
8101
  }
8079
8102
  return scope2;
@@ -8085,7 +8108,7 @@ var stripVirtualFilterOperator = (key, value, scope2) => {
8085
8108
  }
8086
8109
  if (key === "$all") {
8087
8110
  return rewriteFilterClauses(value, (entry) => {
8088
- 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;
8089
8112
  return rewriteFilterObject(entry, (operator, operand) => stripVirtualFilterOperator(operator, operand, scope2));
8090
8113
  });
8091
8114
  }
@@ -8100,7 +8123,7 @@ var stripVirtualFilterQuery = (value, scope2, element = false) => {
8100
8123
  if (isOperatorKey(key)) return element ? stripVirtualFilterOperator(key, child, scope2) : child;
8101
8124
  const fieldScope = resolveVirtualFilterField(key, scope2);
8102
8125
  if (fieldScope === REMOVED_VIRTUAL_FILTER) return REMOVED_VIRTUAL_FILTER;
8103
- if (!fieldScope || !isPlainObject17(child)) return child;
8126
+ if (!fieldScope || !isPlainObject18(child)) return child;
8104
8127
  if (Object.keys(child).some(isOperatorKey)) {
8105
8128
  return rewriteFilterObject(
8106
8129
  child,
@@ -8124,7 +8147,7 @@ function getCorrelatedTemplateDepth(includes) {
8124
8147
  const visit = (entries, depth) => {
8125
8148
  const list = castArray2(entries).filter(Boolean);
8126
8149
  for (const entry of list) {
8127
- if (!isPlainObject17(entry)) continue;
8150
+ if (!isPlainObject18(entry)) continue;
8128
8151
  max = Math.max(max, depth);
8129
8152
  const nested = entry.args?.include;
8130
8153
  if (nested !== void 0) {
@@ -8153,7 +8176,7 @@ function validateClientFilter(filter2) {
8153
8176
  value.forEach((item, index) => visit(item, `${path}[${index}]`));
8154
8177
  return;
8155
8178
  }
8156
- if (!isPlainObject17(value)) return;
8179
+ if (!isPlainObject18(value)) return;
8157
8180
  Object.entries(value).forEach(([key, child]) => {
8158
8181
  const nextPath = path ? `${path}.${key}` : key;
8159
8182
  if (blockedOperators.has(key)) {
@@ -8435,9 +8458,9 @@ var Base = class {
8435
8458
  const walk = (node, prefix) => {
8436
8459
  for (const [key, value] of Object.entries(node)) {
8437
8460
  if (value === void 0) continue;
8438
- if (isPlainObject17(value) && "sub" in value) {
8461
+ if (isPlainObject18(value) && "sub" in value) {
8439
8462
  const sub = value.sub;
8440
- if (isPlainObject17(sub)) walk(sub, prefix ? `${prefix}.${key}` : key);
8463
+ if (isPlainObject18(sub)) walk(sub, prefix ? `${prefix}.${key}` : key);
8441
8464
  continue;
8442
8465
  }
8443
8466
  out.push(prefix ? `${prefix}.${key}` : key);
@@ -8490,7 +8513,7 @@ var Base = class {
8490
8513
  this.throwClientRequestError("bad_request" /* BadRequest */, `Model ${modelName} not found`);
8491
8514
  }
8492
8515
  for (const entry of entries) {
8493
- if (!isPlainObject17(entry)) continue;
8516
+ if (!isPlainObject18(entry)) continue;
8494
8517
  const models = visited.get(entry) ?? /* @__PURE__ */ new Set();
8495
8518
  if (models.has(modelName)) continue;
8496
8519
  models.add(modelName);
@@ -9109,7 +9132,7 @@ import {
9109
9132
  get as get13,
9110
9133
  isArray as isArray10,
9111
9134
  isBoolean as isBoolean4,
9112
- isPlainObject as isPlainObject24,
9135
+ isPlainObject as isPlainObject25,
9113
9136
  omit,
9114
9137
  set as set10,
9115
9138
  uniq as uniq2,
@@ -9210,7 +9233,7 @@ var model_default = Model;
9210
9233
  // src/acl/distinct-field-authorization.ts
9211
9234
  init_field_authorization();
9212
9235
  init_field_catalog();
9213
- import { isPlainObject as isPlainObject18 } from "@web-ts-toolkit/utils";
9236
+ import { isPlainObject as isPlainObject19 } from "@web-ts-toolkit/utils";
9214
9237
  var schemaVariants = (schema) => Object.values(schema.discriminators ?? {});
9215
9238
  var scalarTypes = /* @__PURE__ */ new Set([
9216
9239
  "String",
@@ -9290,8 +9313,8 @@ function isDistinctFieldAuthorized(args) {
9290
9313
  if (path !== field && !path.startsWith(`${field}.`) && !field.startsWith(`${path}.`)) continue;
9291
9314
  if (path.startsWith(`${field}.`) && (!eligible(path) || !schemaShape(schema, path) || !granted(path)))
9292
9315
  return false;
9293
- if (isPlainObject18(raw) && raw.sub !== void 0) {
9294
- 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;
9295
9318
  }
9296
9319
  }
9297
9320
  return true;
@@ -9469,7 +9492,7 @@ init_populate_target();
9469
9492
 
9470
9493
  // src/helpers/apply-update.ts
9471
9494
  init_lib();
9472
- 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";
9473
9496
  function applyUpdate(target, data, allowedFields) {
9474
9497
  const wholeFields = /* @__PURE__ */ new Set();
9475
9498
  const partialContainers = /* @__PURE__ */ new Set();
@@ -9489,7 +9512,7 @@ function applyUpdate(target, data, allowedFields) {
9489
9512
  const apply = (values, prefix = "") => {
9490
9513
  for (const [key, value] of Object.entries(values)) {
9491
9514
  const path = prefix ? `${prefix}.${key}` : key;
9492
- 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))) {
9493
9516
  apply(value, path);
9494
9517
  } else if (isDocument2(target)) {
9495
9518
  target.set(path, value);
@@ -9506,7 +9529,7 @@ init_document();
9506
9529
  init_request_work();
9507
9530
  init_logger_helpers();
9508
9531
  init_request_complexity();
9509
- 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";
9510
9533
  import { mongo as mongo5 } from "mongoose";
9511
9534
 
9512
9535
  // src/acl/select-resolution.ts
@@ -9623,7 +9646,7 @@ init_field_catalog();
9623
9646
  init_service_authorization();
9624
9647
  var own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
9625
9648
  var inside = (path, root) => path === root || path.startsWith(`${root}.`);
9626
- var record2 = (value) => isPlainObject23(value);
9649
+ var record2 = (value) => isPlainObject24(value);
9627
9650
  function inputValue(value) {
9628
9651
  if (typeof value === "function" || typeof value === "symbol") {
9629
9652
  authorizationError("input values must be persisted data, not functions or symbols");
@@ -10979,10 +11002,6 @@ var Service = class extends Base {
10979
11002
  const target = captured.relatedTargets.find((target2) => target2.path === descriptor.path);
10980
11003
  const modelName = target?.modelName;
10981
11004
  if (!modelName) continue;
10982
- if (target.kind === "passthrough") {
10983
- result2.push({ path: descriptor.path, select: descriptor.select });
10984
- continue;
10985
- }
10986
11005
  const policy = relatedPolicy(state.authorization, state.binding, descriptor.path);
10987
11006
  const svc = this.requestCore.getPublicService(modelName);
10988
11007
  const child = createRelatedExecution(
@@ -11001,7 +11020,7 @@ var Service = class extends Base {
11001
11020
  if (isExecutionRowDenied(svc, child)) continue;
11002
11021
  const childState = readServiceExecution(svc, child);
11003
11022
  const targetConfiguration = readServiceAuthorization(childState.authorization, childState.binding).configuration;
11004
- if (policy?.operation.mode !== "grant" && !await this.hookGate.run(
11023
+ if (targetConfiguration.requireOperationAccessForPopulateQueries !== false && policy?.operation.mode !== "grant" && !await this.hookGate.run(
11005
11024
  () => evaluateRouteGuard(
11006
11025
  this.req,
11007
11026
  getAuthorizationPermissions(childState.authorization, childState.binding),
@@ -11053,7 +11072,7 @@ var Service = class extends Base {
11053
11072
  if (this.isScalarPopulateValue(rawTarget)) return rawTarget;
11054
11073
  const isDoc = (() => {
11055
11074
  try {
11056
- if (isPlainObject24(rawTarget)) return true;
11075
+ if (isPlainObject25(rawTarget)) return true;
11057
11076
  const maybe = rawTarget;
11058
11077
  if (maybe && typeof maybe === "object" && "_doc" in maybe) return true;
11059
11078
  if (maybe && typeof maybe.toObject === "function") return true;
@@ -13456,7 +13475,7 @@ init_helpers();
13456
13475
 
13457
13476
  // src/processors.ts
13458
13477
  init_permissions_alias();
13459
- 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";
13460
13479
  function cloneProcessorValue(value) {
13461
13480
  const copy = cloneDeep5(value);
13462
13481
  copyPermissionsAliasGraph(value, copy);
@@ -13496,7 +13515,7 @@ function resolveLeafParents(obj, src) {
13496
13515
  for (let ind = 0; ind < segs.length - 1; ind += 1) {
13497
13516
  const seg = segs[ind];
13498
13517
  targets = targets.reduce((ret, target) => {
13499
- if (!isPlainObject25(target)) return ret;
13518
+ if (!isPlainObject26(target)) return ret;
13500
13519
  const targetObject = target;
13501
13520
  const next = targetObject[seg];
13502
13521
  if (isArray11(next)) {
@@ -13510,7 +13529,7 @@ function resolveLeafParents(obj, src) {
13510
13529
  const leaf = segs[segs.length - 1];
13511
13530
  const pairs = [];
13512
13531
  forEach5(targets, (target) => {
13513
- if (!isPlainObject25(target)) return;
13532
+ if (!isPlainObject26(target)) return;
13514
13533
  pairs.push({ parent: target, leaf });
13515
13534
  });
13516
13535
  return pairs;
@@ -13531,7 +13550,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
13531
13550
  forEach5(segs, (seg, ind) => {
13532
13551
  if (segs.length === ind + 1) {
13533
13552
  forEach5(targets, (target) => {
13534
- if (!isPlainObject25(target)) return;
13553
+ if (!isPlainObject26(target)) return;
13535
13554
  const targetObject = target;
13536
13555
  const leaf = targetObject[seg];
13537
13556
  const sourceForDest = leaf;
@@ -13539,7 +13558,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
13539
13558
  if (isArray11(leaf)) {
13540
13559
  if (leaf.length === 0) {
13541
13560
  depopulated = [];
13542
- } else if (leaf.every((item) => isPlainObject25(item))) {
13561
+ } else if (leaf.every((item) => isPlainObject26(item))) {
13543
13562
  depopulated = map(leaf, idField).map((id, i) => {
13544
13563
  if (id === void 0) {
13545
13564
  failMissingId(`${src}[${i}]`);
@@ -13549,7 +13568,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
13549
13568
  } else {
13550
13569
  return;
13551
13570
  }
13552
- } else if (isPlainObject25(leaf)) {
13571
+ } else if (isPlainObject26(leaf)) {
13553
13572
  const id = get14(leaf, idField);
13554
13573
  if (id === void 0) {
13555
13574
  failMissingId(src);
@@ -13563,7 +13582,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
13563
13582
  });
13564
13583
  } else {
13565
13584
  targets = targets.reduce((ret, target) => {
13566
- if (!isPlainObject25(target)) return ret;
13585
+ if (!isPlainObject26(target)) return ret;
13567
13586
  const targetObject = target;
13568
13587
  const next = targetObject[seg];
13569
13588
  if (isArray11(next)) {
@@ -13669,7 +13688,7 @@ var countPaths = (docObject, operations, options = { mutable: true }) => {
13669
13688
  let count;
13670
13689
  if (isArray11(leafValue)) {
13671
13690
  count = leafValue.length;
13672
- } else if (isPlainObject25(leafValue)) {
13691
+ } else if (isPlainObject26(leafValue)) {
13673
13692
  count = Object.keys(leafValue).length;
13674
13693
  } else if (leafValue === null || typeof leafValue === "undefined") {
13675
13694
  count = 0;
@@ -13747,6 +13766,8 @@ init_core_shared();
13747
13766
  init_virtual_projection();
13748
13767
  init_populate_target();
13749
13768
  init_model_output_planning();
13769
+ init_authorization_snapshot();
13770
+ init_document();
13750
13771
  init_output_field_filter();
13751
13772
  init_request_work();
13752
13773
  var emptyPermissionLeaf = () => ({ $: "_" });
@@ -13857,17 +13878,11 @@ var Core = class {
13857
13878
  return this.genSelect(modelName, access, targetFields, options);
13858
13879
  }
13859
13880
  /** Persisted request-only populate admission; fetch planning is not field authority. */
13860
- async requestPopulateSelect(modelName, access, targetFields = null, scopePath = []) {
13881
+ async requestPopulateSelect(configuration, permissions, access, targetFields = null, scopePath = []) {
13861
13882
  const subPaths = scopePath.flatMap((part) => [part, "sub"]);
13862
- const permissionSchema = getModelOption(modelName, ["permissionSchema"].concat(subPaths).join("."));
13863
- const permissions = this.getGlobalPermissions();
13864
- let virtualNames;
13865
- try {
13866
- const runtime = getActiveRuntime() ?? defaultRuntime;
13867
- virtualNames = runtime.getVirtualNames(modelName, subPaths);
13868
- } catch {
13869
- virtualNames = [];
13870
- }
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("."));
13871
13886
  return resolveSelectForRequest({
13872
13887
  req: this.req,
13873
13888
  permissionSchema,
@@ -13886,13 +13901,21 @@ var Core = class {
13886
13901
  assertPublicAdmission(_populate, "populate");
13887
13902
  assertPopulateAccess(access);
13888
13903
  if (!_populate) return [];
13889
- let populate = Array.isArray(_populate) ? _populate : [_populate];
13890
- for (const p of populate) {
13904
+ const descriptors = copyAuthorizationValue(Array.isArray(_populate) ? _populate : [_populate], true);
13905
+ for (const p of descriptors) {
13891
13906
  if (!isString6(p)) assertPopulateAccess(p.access);
13892
13907
  }
13893
- 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(
13894
13917
  await Promise.all(
13895
- populate.map(async (p) => {
13918
+ descriptors.map(async (p) => {
13896
13919
  const populateAccess = !isString6(p) && p.access ? p.access : access;
13897
13920
  assertPopulateAccess(populateAccess);
13898
13921
  const originalSelect = isString6(p) ? void 0 : p.select;
@@ -13904,78 +13927,86 @@ var Core = class {
13904
13927
  const pathForSourcePermission = scopePrefix && ret.path.startsWith(`${scopePrefix}.`) ? ret.path.slice(scopePrefix.length + 1) : ret.path;
13905
13928
  const parentPath = pathForSourcePermission.includes(".") ? pathForSourcePermission.split(".")[0] : pathForSourcePermission;
13906
13929
  const allowedParentPaths = await this.requestPopulateSelect(
13907
- modelName,
13930
+ sourceConfiguration,
13931
+ permissions,
13908
13932
  populateAccess,
13909
13933
  [parentPath],
13910
13934
  scopePath
13911
13935
  );
13912
13936
  if (!allowedParentPaths.includes(parentPath)) return null;
13913
- const refModelName = getModelRef(modelName, ret.path);
13914
- if (!refModelName) return null;
13915
- const runtime = getActiveRuntime();
13916
- if (!runtime) return null;
13917
- const requireRegisteredPopulateModels = getGlobalOption("requireRegisteredPopulateModels", true);
13918
- if (!runtime.hasModel(refModelName)) {
13919
- return requireRegisteredPopulateModels ? null : ret;
13920
- }
13921
- const allowedTargetOperation = await this.isAllowed(refModelName, populateAccess);
13922
- 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
+ )) {
13923
13949
  return null;
13924
13950
  }
13925
- ret.select = await this.requestPopulateSelect(refModelName, populateAccess, ret.select);
13926
- const hookInputs = getModelOption(refModelName, `alwaysSelectFields.${populateAccess}`, []);
13927
- ret.select = uniq3([...ret.select, ...stripVirtuals(hookInputs, runtime.getVirtualNames(refModelName))]);
13928
- 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
+ });
13929
13975
  if (filter2 === false) return null;
13930
- ret.match = filter2;
13931
- try {
13932
- const planAccess = populateAccess;
13933
- const targetSnapshot = {
13934
- permissionSchema: getModelOption(refModelName, "permissionSchema", null) ?? null,
13935
- virtuals: getModelOption(refModelName, "virtuals", null) ?? null,
13936
- alwaysSelectFields: getModelOption(refModelName, "alwaysSelectFields", null) ?? null,
13937
- modelPermissionPrefix: getModelOption(refModelName, "modelPermissionPrefix", "") ?? "",
13938
- requireExplicitSelect: getModelOption(refModelName, "requireExplicitSelect", false) ?? false,
13939
- documentPermissionField: getModelOption(refModelName, "documentPermissionField", "_permissions") ?? "_permissions",
13940
- exposedDocPermissionKeys: getModelOption(refModelName, "exposedDocPermissionKeys", void 0),
13941
- stripPermissionsField: getModelOption(refModelName, "stripPermissionsField", false) ?? false,
13942
- disableFieldPermissions: getModelOption(refModelName, "disableFieldPermissions", false) ?? false
13943
- };
13944
- const perms = this.getPermissions();
13945
- const globalForPlan = perms && typeof perms.has === "function" && typeof perms.hasKey === "function" ? perms : { has: () => false, hasKey: () => false };
13946
- const targetPlan = planVirtualProjection({
13947
- receivingModelName: refModelName,
13948
- snapshot: {
13949
- permissionSchema: targetSnapshot.permissionSchema,
13950
- virtuals: targetSnapshot.virtuals,
13951
- alwaysSelectFields: targetSnapshot.alwaysSelectFields,
13952
- modelPermissionPrefix: targetSnapshot.modelPermissionPrefix,
13953
- requireExplicitSelect: targetSnapshot.requireExplicitSelect,
13954
- documentPermissionField: targetSnapshot.documentPermissionField
13955
- },
13956
- virtualAccess: planAccess,
13957
- outputAccess: planAccess,
13958
- docPermissionsAccess: planAccess,
13959
- requestedSelect: originalSelect,
13960
- internalFetch: { baseFields: ["_id"] },
13961
- globalPermissions: globalForPlan
13962
- });
13963
- const merged = uniq3([...normalizeSelect(ret.select), ...targetPlan.virtualOnlyDeps ?? []]).filter(
13964
- (t) => t !== "-_id" && !t.includes(".sub.")
13965
- );
13966
- if (!merged.includes("_id")) merged.push("_id");
13967
- ret.select = merged;
13968
- setPopulateTargetMeta(ret, {
13969
- targetModelName: refModelName,
13970
- virtualAccess: planAccess,
13971
- outputAccess: planAccess,
13972
- docPermissionsAccess: planAccess,
13973
- requestedSelect: originalSelect,
13974
- plan: targetPlan,
13975
- snapshot: targetSnapshot
13976
- });
13977
- } catch {
13978
- }
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
+ });
13979
14010
  return ret;
13980
14011
  })
13981
14012
  )
@@ -14299,12 +14330,12 @@ function guard(condition) {
14299
14330
  const permissions = req[PERMISSIONS];
14300
14331
  let cond = condition;
14301
14332
  let phas = (key) => permissions.has(key);
14302
- if (isPlainObject26(condition)) {
14333
+ if (isPlainObject27(condition)) {
14303
14334
  const { modelName, id, condition: _cond } = condition;
14304
14335
  const svc = req.macl.getPublicService(modelName);
14305
14336
  const select = getModelOption(modelName, `alwaysSelectFields.read`, void 0);
14306
14337
  let _id = isString7(id) ? id : null;
14307
- if (isPlainObject26(id)) {
14338
+ if (isPlainObject27(id)) {
14308
14339
  const { type = "param", key } = id;
14309
14340
  if (type === "param") {
14310
14341
  const paramValue = req.params[key];
@@ -14346,7 +14377,7 @@ import express2 from "express";
14346
14377
 
14347
14378
  // src/routers/model-router.ts
14348
14379
  import JsonRouter8 from "@web-ts-toolkit/express-json-router";
14349
- 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";
14350
14381
  init_logger_helpers();
14351
14382
  init_runtime();
14352
14383
  init_runtime_context();
@@ -14480,7 +14511,7 @@ import JsonRouter7 from "@web-ts-toolkit/express-json-router";
14480
14511
 
14481
14512
  // src/validation/common.ts
14482
14513
  import { z as z2 } from "zod";
14483
- import { isPlainObject as isPlainObject27 } from "@web-ts-toolkit/utils";
14514
+ import { isPlainObject as isPlainObject28 } from "@web-ts-toolkit/utils";
14484
14515
 
14485
14516
  // src/validation/public-admission-schema.ts
14486
14517
  init_public_admission();
@@ -14609,7 +14640,7 @@ var correlatedFilterSchema = z2.record(z2.string(), z2.unknown()).superRefine((f
14609
14640
  }
14610
14641
  });
14611
14642
  var correlatedEmptyOptionsSchema = z2.unknown().refine(
14612
- (value) => value === void 0 || isPlainObject27(value) && Object.keys(value).length === 0,
14643
+ (value) => value === void 0 || isPlainObject28(value) && Object.keys(value).length === 0,
14613
14644
  "Correlated include options must be absent or empty"
14614
14645
  ).optional();
14615
14646
  var correlatedPathSchema = z2.string().min(1).superRefine((path, ctx) => {
@@ -14646,7 +14677,7 @@ var correlatedListArgsSchema = z2.object({
14646
14677
  include: z2.lazy(() => correlatedNestedIncludeSchema).optional()
14647
14678
  }).strict().superRefine((args, ctx) => checkArgsMarkers(args.select, args.sort, ctx));
14648
14679
  var correlatedCountArgsSchema = z2.unknown().refine(
14649
- (value) => value === void 0 || isPlainObject27(value) && Object.keys(value).length === 0,
14680
+ (value) => value === void 0 || isPlainObject28(value) && Object.keys(value).length === 0,
14650
14681
  "Count includes accept no args"
14651
14682
  ).optional();
14652
14683
  var correlatedReadByIdSchema = z2.object({
@@ -14699,7 +14730,7 @@ var correlatedNestedIncludeSchema = z2.union([includeItemSchema, z2.array(includ
14699
14730
  var rejectDuplicateCorrelatedPaths = (items, ctx) => {
14700
14731
  const seen = /* @__PURE__ */ new Set();
14701
14732
  items.forEach((item, index) => {
14702
- if (isPlainObject27(item) && item.mode === "correlated" && typeof item.path === "string") {
14733
+ if (isPlainObject28(item) && item.mode === "correlated" && typeof item.path === "string") {
14703
14734
  if (seen.has(item.path)) {
14704
14735
  ctx.addIssue({
14705
14736
  code: z2.ZodIssueCode.custom,
@@ -16700,7 +16731,7 @@ var ModelRouter = class _ModelRouter {
16700
16731
  value
16701
16732
  );
16702
16733
  }
16703
- if (arguments.length === 1 && isPlainObject28(keyOrOptions)) {
16734
+ if (arguments.length === 1 && isPlainObject29(keyOrOptions)) {
16704
16735
  assertMutableRouterOptions("model", keyOrOptions);
16705
16736
  this.runtime.setModelOptions(
16706
16737
  this.modelName,
@@ -17234,7 +17265,7 @@ var RootRouter = class {
17234
17265
 
17235
17266
  // src/routers/data-router.ts
17236
17267
  import JsonRouter10 from "@web-ts-toolkit/express-json-router";
17237
- 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";
17238
17269
  init_runtime();
17239
17270
  init_helpers();
17240
17271
  init_schemas();
@@ -17450,7 +17481,7 @@ var DataRouter = class {
17450
17481
  value
17451
17482
  );
17452
17483
  }
17453
- if (arguments.length === 1 && isPlainObject29(keyOrOptions)) {
17484
+ if (arguments.length === 1 && isPlainObject30(keyOrOptions)) {
17454
17485
  assertMutableRouterOptions("data", keyOrOptions);
17455
17486
  this.runtime.setDataOptions(this.dataName, keyOrOptions);
17456
17487
  }
@@ -17609,7 +17640,7 @@ function createRuntimeApi(runtime) {
17609
17640
  if (arguments.length === 2 && isString10(keyOrOptions)) {
17610
17641
  return runtime.setGlobalOption(keyOrOptions, value);
17611
17642
  }
17612
- if (arguments.length === 1 && isPlainObject30(keyOrOptions)) {
17643
+ if (arguments.length === 1 && isPlainObject31(keyOrOptions)) {
17613
17644
  return runtime.setGlobalOptions(keyOrOptions);
17614
17645
  }
17615
17646
  };