@web-ts-toolkit/access-router 0.43.0 → 0.45.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/advanced.js CHANGED
@@ -134,6 +134,12 @@ var import_utils8 = require("@web-ts-toolkit/utils");
134
134
  var import_utils = require("@web-ts-toolkit/utils");
135
135
  var DEFAULT_LIST_HARD_LIMIT = 1e3;
136
136
 
137
+ // src/helpers/sort-policy.ts
138
+ var fieldPathPattern = /^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)*$/;
139
+ function isValidFieldPath(field) {
140
+ return typeof field === "string" && fieldPathPattern.test(field);
141
+ }
142
+
137
143
  // src/helpers/index.ts
138
144
  var import_utils3 = require("@web-ts-toolkit/utils");
139
145
 
@@ -557,6 +563,29 @@ var cloneConfigValue = (value) => {
557
563
  ])
558
564
  );
559
565
  };
566
+ var cloneForRead = (value) => {
567
+ if (Array.isArray(value)) {
568
+ if (Object.isFrozen(value)) {
569
+ return value;
570
+ }
571
+ return value.map((item) => cloneForRead(item));
572
+ }
573
+ if (!value || typeof value !== "object") {
574
+ return value;
575
+ }
576
+ if (Object.getPrototypeOf(value) !== Object.prototype) {
577
+ return value;
578
+ }
579
+ if (Object.isFrozen(value)) {
580
+ return value;
581
+ }
582
+ return Object.fromEntries(
583
+ Object.entries(value).map(([entryKey, entryValue]) => [
584
+ entryKey,
585
+ cloneForRead(entryValue)
586
+ ])
587
+ );
588
+ };
560
589
  var freezeConfigValue = (value) => {
561
590
  if (!value || typeof value !== "object") {
562
591
  return value;
@@ -572,6 +601,7 @@ var freezeConfigValue = (value) => {
572
601
  }
573
602
  return value;
574
603
  };
604
+ var cloneAndFreezeForStore = (value) => freezeConfigValue(cloneForRead(value));
575
605
  var getNestedOption = (manager, key, defaultValue) => {
576
606
  const keys2 = String(key).split(".");
577
607
  if (keys2.length === 1) {
@@ -601,6 +631,14 @@ var OptionsManager = class {
601
631
  target[keystr] = value;
602
632
  _this.listeners[keystr] && _this.listeners[keystr].call(_this, value, keystr, target, oldvalue);
603
633
  return true;
634
+ },
635
+ defineProperty(target, key, descriptor) {
636
+ const keystr = String(key);
637
+ const oldvalue = target[keystr];
638
+ const result = Reflect.defineProperty(target, key, descriptor);
639
+ const newValue = target[keystr];
640
+ _this.listeners[keystr] && _this.listeners[keystr].call(_this, newValue, keystr, target, oldvalue);
641
+ return result;
604
642
  }
605
643
  });
606
644
  }
@@ -614,18 +652,18 @@ var OptionsManager = class {
614
652
  set(key, value) {
615
653
  const path = toPath(key);
616
654
  if (path.length <= 1) {
617
- (0, import_utils6.set)(this.currentOptions, path, this.preserveKeys.has(String(path[0])) ? value : cloneConfigValue(value));
655
+ (0, import_utils6.set)(this.currentOptions, path, this.preserveKeys.has(String(path[0])) ? value : cloneAndFreezeForStore(value));
618
656
  return;
619
657
  }
620
658
  const [rootKey, ...nestedPath] = path;
621
659
  const currentRoot = (0, import_utils6.get)(this.currentOptions, [rootKey], void 0);
622
660
  const baseRoot = (0, import_utils6.isPlainObject)(currentRoot) ? currentRoot : {};
623
661
  const nextRoot = cloneConfigValue(baseRoot);
624
- (0, import_utils6.set)(nextRoot, nestedPath, cloneConfigValue(value));
625
- (0, import_utils6.set)(this.currentOptions, [rootKey], nextRoot);
662
+ (0, import_utils6.set)(nextRoot, nestedPath, cloneForRead(value));
663
+ (0, import_utils6.set)(this.currentOptions, [rootKey], freezeConfigValue(nextRoot));
626
664
  }
627
665
  fetch() {
628
- const cloned = cloneConfigValue(this.currentOptions);
666
+ const cloned = cloneForRead(this.currentOptions);
629
667
  for (const key of this.preserveKeys) {
630
668
  if (key in this.currentOptions) {
631
669
  cloned[key] = this.currentOptions[key];
@@ -634,20 +672,20 @@ var OptionsManager = class {
634
672
  return freezeConfigValue(cloned);
635
673
  }
636
674
  assign(options) {
637
- const cloned = cloneConfigValue(options);
675
+ const cloned = cloneForRead(options);
638
676
  for (const key of this.preserveKeys) {
639
677
  if (key in options) {
640
678
  cloned[key] = options[key];
641
679
  }
642
680
  }
643
- (0, import_utils6.assign)(this.currentOptions, cloned);
681
+ (0, import_utils6.assign)(this.currentOptions, freezeConfigValue(cloned));
644
682
  }
645
683
  onchange(key, func) {
646
684
  (0, import_utils6.set)(this.listeners, key, func);
647
685
  return this;
648
686
  }
649
687
  snapshot() {
650
- const cloned = cloneConfigValue(this.currentOptions);
688
+ const cloned = cloneForRead(this.currentOptions);
651
689
  for (const key of this.preserveKeys) {
652
690
  if (key in this.currentOptions) {
653
691
  cloned[key] = this.currentOptions[key];
@@ -663,13 +701,13 @@ var OptionsManager = class {
663
701
  delete current[key];
664
702
  }
665
703
  }
666
- const cloned = cloneConfigValue(snap);
704
+ const cloned = cloneForRead(snap);
667
705
  for (const key of this.preserveKeys) {
668
706
  if (key in snap) {
669
707
  cloned[key] = snap[key];
670
708
  }
671
709
  }
672
- (0, import_utils6.assign)(current, cloned);
710
+ (0, import_utils6.assign)(current, freezeConfigValue(cloned));
673
711
  }
674
712
  };
675
713
 
@@ -684,7 +722,9 @@ var defaultRequestComplexity = {
684
722
  maxIncludeCount: 10,
685
723
  maxSubQueryCount: 10,
686
724
  maxBulkConcurrency: 10,
687
- maxHookConcurrency: 10
725
+ maxHookConcurrency: 10,
726
+ maxCorrelatedQueries: 100,
727
+ maxCorrelatedDepth: 5
688
728
  };
689
729
  var normalizePositiveInteger = (value, fallback) => {
690
730
  return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : fallback;
@@ -704,6 +744,14 @@ var resolveRequestComplexity = (options) => ({
704
744
  maxHookConcurrency: normalizePositiveInteger(
705
745
  options?.maxHookConcurrency,
706
746
  defaultRequestComplexity.maxHookConcurrency
747
+ ),
748
+ maxCorrelatedQueries: normalizePositiveInteger(
749
+ options?.maxCorrelatedQueries,
750
+ defaultRequestComplexity.maxCorrelatedQueries
751
+ ),
752
+ maxCorrelatedDepth: normalizePositiveInteger(
753
+ options?.maxCorrelatedDepth,
754
+ defaultRequestComplexity.maxCorrelatedDepth
707
755
  )
708
756
  });
709
757
  function validateRequestComplexity(value, options, scope = "request") {
@@ -1139,8 +1187,7 @@ var AccessRuntime = class {
1139
1187
  }
1140
1188
  setDataOptions(dataName, options) {
1141
1189
  const manager = this.getOrCreateDataOptions(dataName);
1142
- const currentDataOptions = manager.fetch();
1143
- manager.assign({ ...currentDataOptions, ...options });
1190
+ manager.assign(options);
1144
1191
  }
1145
1192
  setDataOption(dataName, key, value) {
1146
1193
  const manager = this.getOrCreateDataOptions(dataName);
@@ -1150,6 +1197,11 @@ var AccessRuntime = class {
1150
1197
  const manager = this.getOrCreateDataOptions(dataName);
1151
1198
  return manager.fetch();
1152
1199
  }
1200
+ getDataSnapshot(dataName) {
1201
+ const manager = this.getOrCreateDataOptions(dataName);
1202
+ const snapshot = manager.get("data");
1203
+ return snapshot ?? [];
1204
+ }
1153
1205
  getDataOption(dataName, key, defaultValue) {
1154
1206
  const manager = this.getOrCreateDataOptions(dataName);
1155
1207
  return getNestedOption(manager, key, defaultValue);
@@ -1208,6 +1260,175 @@ var getGlobalOption = (key, defaultValue) => {
1208
1260
 
1209
1261
  // src/validation/common.ts
1210
1262
  var import_zod2 = require("zod");
1263
+ var import_utils10 = require("@web-ts-toolkit/utils");
1264
+
1265
+ // src/correlated-includes.ts
1266
+ var import_utils9 = require("@web-ts-toolkit/utils");
1267
+ var PARENT_MARKER_KEY = "$parent";
1268
+ var ESCAPE_WRAPPER_KEY = "$escape";
1269
+ var SUBQUERY_PAYLOAD_KEYS = /* @__PURE__ */ new Set(["$$sq", "$$date"]);
1270
+ var MARKER_FORBIDDEN_OPERATOR_KEYS = /* @__PURE__ */ new Set(["$text", "$where", "$comment"]);
1271
+ var LOGICAL_CLAUSE_KEYS = /* @__PURE__ */ new Set(["$and", "$or", "$nor"]);
1272
+ var DANGEROUS_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
1273
+ var FIELD_SEGMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_$]*$/;
1274
+ var ARRAY_INDEX_SEGMENT_PATTERN = /^(0|[1-9]\d*)$/;
1275
+ var ownKeys = (value) => Object.keys(value);
1276
+ function isParentRefMarker(value) {
1277
+ return (0, import_utils9.isPlainObject)(value) && ownKeys(value).length === 1 && ownKeys(value)[0] === PARENT_MARKER_KEY;
1278
+ }
1279
+ function isEscapedParentRef(value) {
1280
+ return (0, import_utils9.isPlainObject)(value) && ownKeys(value).length === 1 && ownKeys(value)[0] === ESCAPE_WRAPPER_KEY;
1281
+ }
1282
+ function hasMarkerKey(value) {
1283
+ return ownKeys(value).includes(PARENT_MARKER_KEY) || ownKeys(value).includes(ESCAPE_WRAPPER_KEY);
1284
+ }
1285
+ function isValidParentRefPath(path) {
1286
+ if (typeof path !== "string" || path.length === 0) return false;
1287
+ const segments = path.split(".");
1288
+ return segments.every(
1289
+ (segment) => (FIELD_SEGMENT_PATTERN.test(segment) || ARRAY_INDEX_SEGMENT_PATTERN.test(segment)) && !DANGEROUS_PATH_SEGMENTS.has(segment)
1290
+ );
1291
+ }
1292
+ function isValidCorrelatedOutputPath(path) {
1293
+ return typeof path === "string" && path.length > 0 && isValidFieldPath(path) && path !== "_id" && !path.startsWith("$");
1294
+ }
1295
+ function containsParentMarkerShape(value) {
1296
+ const seen = /* @__PURE__ */ new Set();
1297
+ const visit = (current) => {
1298
+ if (isParentRefMarker(current) || isEscapedParentRef(current)) return true;
1299
+ if ((0, import_utils9.isArray)(current)) {
1300
+ if (seen.has(current)) return false;
1301
+ seen.add(current);
1302
+ return current.some(visit);
1303
+ }
1304
+ if ((0, import_utils9.isPlainObject)(current)) {
1305
+ if (seen.has(current)) return false;
1306
+ seen.add(current);
1307
+ if (hasMarkerKey(current)) return true;
1308
+ return Object.values(current).some(visit);
1309
+ }
1310
+ return false;
1311
+ };
1312
+ return visit(value);
1313
+ }
1314
+ function scanTemplateValueNode(value, path, issues) {
1315
+ if (isEscapedParentRef(value)) {
1316
+ const inner = value.$escape;
1317
+ if (!isParentRefMarker(inner) || typeof inner.$parent !== "string" || (inner.$parent?.length ?? 0) === 0) {
1318
+ issues.push({
1319
+ message: 'Invalid $escape payload: expected { "$escape": { "$parent": "<field>" } }',
1320
+ path
1321
+ });
1322
+ }
1323
+ return;
1324
+ }
1325
+ if (isParentRefMarker(value)) {
1326
+ if (!isValidParentRefPath(value.$parent)) {
1327
+ issues.push({
1328
+ message: `Invalid $parent reference path at ${path.length === 0 ? "#" : path.join(".")}`,
1329
+ path
1330
+ });
1331
+ }
1332
+ return;
1333
+ }
1334
+ if ((0, import_utils9.isPlainObject)(value)) {
1335
+ if (hasMarkerKey(value)) {
1336
+ issues.push({
1337
+ message: 'Malformed $parent reference: objects carrying $parent must be exactly { "$parent": "<field>" }',
1338
+ path
1339
+ });
1340
+ return;
1341
+ }
1342
+ scanTemplateObjectEntries(value, path, issues);
1343
+ return;
1344
+ }
1345
+ if ((0, import_utils9.isArray)(value)) {
1346
+ value.forEach((item, index) => scanTemplateValueNode(item, [...path, index], issues));
1347
+ }
1348
+ }
1349
+ function scanTemplateObjectEntries(obj, basePath, issues) {
1350
+ for (const [key, child] of Object.entries(obj)) {
1351
+ const childPath = [...basePath, key];
1352
+ if (key === PARENT_MARKER_KEY) {
1353
+ issues.push({ message: "$parent is never a field name in correlated templates", path: childPath });
1354
+ continue;
1355
+ }
1356
+ if (SUBQUERY_PAYLOAD_KEYS.has(key)) {
1357
+ if (containsParentMarkerShape(child)) {
1358
+ issues.push({
1359
+ message: `Unsupported $parent reference inside ${key}: markers are not resolved in subquery payloads`,
1360
+ path: childPath
1361
+ });
1362
+ }
1363
+ continue;
1364
+ }
1365
+ if (MARKER_FORBIDDEN_OPERATOR_KEYS.has(key)) {
1366
+ if (containsParentMarkerShape(child)) {
1367
+ issues.push({
1368
+ message: `Unsupported $parent reference inside ${key}: markers are not resolved in ${key} values`,
1369
+ path: childPath
1370
+ });
1371
+ }
1372
+ continue;
1373
+ }
1374
+ if (key === "$elemMatch") {
1375
+ if (isParentRefMarker(child) || isEscapedParentRef(child)) {
1376
+ issues.push({
1377
+ message: "Unsupported $parent reference as a direct $elemMatch value",
1378
+ path: childPath
1379
+ });
1380
+ continue;
1381
+ }
1382
+ scanTemplateValueNode(child, childPath, issues);
1383
+ continue;
1384
+ }
1385
+ if (LOGICAL_CLAUSE_KEYS.has(key)) {
1386
+ if (!(0, import_utils9.isArray)(child)) {
1387
+ scanTemplateValueNode(child, childPath, issues);
1388
+ continue;
1389
+ }
1390
+ child.forEach((clause, index) => {
1391
+ const clausePath = [...childPath, index];
1392
+ if (isParentRefMarker(clause) || isEscapedParentRef(clause)) {
1393
+ issues.push({
1394
+ message: `Unsupported $parent reference as a direct ${key} clause: clauses must be filter objects`,
1395
+ path: clausePath
1396
+ });
1397
+ return;
1398
+ }
1399
+ if ((0, import_utils9.isPlainObject)(clause)) {
1400
+ scanTemplateObjectEntries(clause, clausePath, issues);
1401
+ }
1402
+ });
1403
+ continue;
1404
+ }
1405
+ scanTemplateValueNode(child, childPath, issues);
1406
+ }
1407
+ }
1408
+ function validateCorrelatedFilterTemplate(filter) {
1409
+ const issues = [];
1410
+ if (!(0, import_utils9.isPlainObject)(filter)) {
1411
+ issues.push({ message: "Correlated include filter must be an object", path: [] });
1412
+ return issues;
1413
+ }
1414
+ if (isParentRefMarker(filter) || isEscapedParentRef(filter)) {
1415
+ issues.push({ message: "Correlated include filter must not itself be a $parent reference", path: [] });
1416
+ return issues;
1417
+ }
1418
+ scanTemplateObjectEntries(filter, [], issues);
1419
+ return issues;
1420
+ }
1421
+ function findArgsMarkerIssue(value, where) {
1422
+ if (value !== void 0 && containsParentMarkerShape(value)) {
1423
+ return {
1424
+ message: `Unsupported $parent reference in ${where}: references are only supported in filter and id positions`,
1425
+ path: []
1426
+ };
1427
+ }
1428
+ return null;
1429
+ }
1430
+
1431
+ // src/validation/common.ts
1211
1432
  var stringOrStringArray = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.array(import_zod2.z.string())]);
1212
1433
  var safeIntegerString = (min, message) => import_zod2.z.string().regex(/^\d+$/, message).refine((value) => Number.isSafeInteger(Number(value)), "Expected a safe integer").refine((value) => Number(value) >= min, message);
1213
1434
  var queryBooleanString = import_zod2.z.enum(["true", "false"]);
@@ -1268,7 +1489,8 @@ var subPopulateSchema = import_zod2.z.union([
1268
1489
  select: projectionSchema.optional()
1269
1490
  }).passthrough()
1270
1491
  ]);
1271
- var includeItemSchema = import_zod2.z.object({
1492
+ var legacyIncludeItemSchema = import_zod2.z.object({
1493
+ mode: import_zod2.z.enum(["legacy"]).optional(),
1272
1494
  model: import_zod2.z.string().min(1),
1273
1495
  op: import_zod2.z.enum(["list", "read", "count"]),
1274
1496
  path: import_zod2.z.string().min(1),
@@ -1277,8 +1499,139 @@ var includeItemSchema = import_zod2.z.object({
1277
1499
  foreignField: import_zod2.z.string().min(1),
1278
1500
  args: import_zod2.z.unknown().optional().describe("Forwarded to list/read includes. Count includes ignore pagination fields so counts remain exact."),
1279
1501
  options: import_zod2.z.unknown().optional()
1280
- }).passthrough();
1281
- var includeSchema = import_zod2.z.union([includeItemSchema, import_zod2.z.array(includeItemSchema)]);
1502
+ }).passthrough().superRefine((entry, ctx) => {
1503
+ if (entry.filter !== void 0 && containsParentMarkerShape(entry.filter)) {
1504
+ ctx.addIssue({
1505
+ code: import_zod2.z.ZodIssueCode.custom,
1506
+ message: 'Legacy includes must not contain $parent markers; use a correlated include (mode: "correlated")',
1507
+ path: ["filter"]
1508
+ });
1509
+ }
1510
+ });
1511
+ var parentRefSchema = import_zod2.z.object({ $parent: import_zod2.z.string().min(1) }).strict().superRefine((value, ctx) => {
1512
+ if (!isValidParentRefPath(value.$parent)) {
1513
+ ctx.addIssue({
1514
+ code: import_zod2.z.ZodIssueCode.custom,
1515
+ message: `Invalid $parent reference path: ${value.$parent}`,
1516
+ path: ["$parent"]
1517
+ });
1518
+ }
1519
+ });
1520
+ var correlatedIdSchema = import_zod2.z.union([import_zod2.z.string().min(1), parentRefSchema]);
1521
+ var correlatedFilterSchema = import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).superRefine((filter, ctx) => {
1522
+ for (const issue of validateCorrelatedFilterTemplate(filter)) {
1523
+ ctx.addIssue({
1524
+ code: import_zod2.z.ZodIssueCode.custom,
1525
+ message: issue.message,
1526
+ path: issue.path.map(String)
1527
+ });
1528
+ }
1529
+ });
1530
+ var correlatedEmptyOptionsSchema = import_zod2.z.unknown().refine(
1531
+ (value) => value === void 0 || (0, import_utils10.isPlainObject)(value) && Object.keys(value).length === 0,
1532
+ "Correlated include options must be absent or empty"
1533
+ ).optional();
1534
+ var correlatedPathSchema = import_zod2.z.string().min(1).superRefine((path, ctx) => {
1535
+ if (!isValidCorrelatedOutputPath(path)) {
1536
+ ctx.addIssue({
1537
+ code: import_zod2.z.ZodIssueCode.custom,
1538
+ message: `Invalid correlated include output path: ${path}`,
1539
+ path: []
1540
+ });
1541
+ }
1542
+ });
1543
+ var checkArgsMarkers = (select, sort, ctx) => {
1544
+ const selectIssue = findArgsMarkerIssue(select, "args.select");
1545
+ if (selectIssue) {
1546
+ ctx.addIssue({ code: import_zod2.z.ZodIssueCode.custom, message: selectIssue.message, path: ["select"] });
1547
+ }
1548
+ const sortIssue = findArgsMarkerIssue(sort, "args.sort");
1549
+ if (sortIssue) {
1550
+ ctx.addIssue({ code: import_zod2.z.ZodIssueCode.custom, message: sortIssue.message, path: ["sort"] });
1551
+ }
1552
+ };
1553
+ var correlatedReadArgsSchema = import_zod2.z.object({
1554
+ select: projectionSchema.optional(),
1555
+ sort: sortSchema.optional(),
1556
+ include: import_zod2.z.lazy(() => correlatedNestedIncludeSchema).optional()
1557
+ }).strict().superRefine((args, ctx) => checkArgsMarkers(args.select, args.sort, ctx));
1558
+ var correlatedListArgsSchema = import_zod2.z.object({
1559
+ select: projectionSchema.optional(),
1560
+ sort: sortSchema.optional(),
1561
+ skip: import_zod2.z.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
1562
+ limit: import_zod2.z.union([positiveIntegerSchema, positiveIntegerString]).optional(),
1563
+ page: import_zod2.z.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
1564
+ pageSize: import_zod2.z.union([positiveIntegerSchema, positiveIntegerString]).optional(),
1565
+ include: import_zod2.z.lazy(() => correlatedNestedIncludeSchema).optional()
1566
+ }).strict().superRefine((args, ctx) => checkArgsMarkers(args.select, args.sort, ctx));
1567
+ var correlatedCountArgsSchema = import_zod2.z.unknown().refine(
1568
+ (value) => value === void 0 || (0, import_utils10.isPlainObject)(value) && Object.keys(value).length === 0,
1569
+ "Count includes accept no args"
1570
+ ).optional();
1571
+ var correlatedReadByIdSchema = import_zod2.z.object({
1572
+ mode: import_zod2.z.literal("correlated"),
1573
+ model: import_zod2.z.string().min(1),
1574
+ op: import_zod2.z.literal("read"),
1575
+ path: correlatedPathSchema,
1576
+ id: correlatedIdSchema,
1577
+ args: correlatedReadArgsSchema.optional(),
1578
+ options: correlatedEmptyOptionsSchema
1579
+ }).strict();
1580
+ var correlatedReadByFilterSchema = import_zod2.z.object({
1581
+ mode: import_zod2.z.literal("correlated"),
1582
+ model: import_zod2.z.string().min(1),
1583
+ op: import_zod2.z.literal("read"),
1584
+ path: correlatedPathSchema,
1585
+ filter: correlatedFilterSchema,
1586
+ args: correlatedReadArgsSchema.optional(),
1587
+ options: correlatedEmptyOptionsSchema
1588
+ }).strict();
1589
+ var correlatedListSchema = import_zod2.z.object({
1590
+ mode: import_zod2.z.literal("correlated"),
1591
+ model: import_zod2.z.string().min(1),
1592
+ op: import_zod2.z.literal("list"),
1593
+ path: correlatedPathSchema,
1594
+ filter: correlatedFilterSchema,
1595
+ args: correlatedListArgsSchema.optional(),
1596
+ options: correlatedEmptyOptionsSchema
1597
+ }).strict();
1598
+ var correlatedCountSchema = import_zod2.z.object({
1599
+ mode: import_zod2.z.literal("correlated"),
1600
+ model: import_zod2.z.string().min(1),
1601
+ op: import_zod2.z.literal("count"),
1602
+ path: correlatedPathSchema,
1603
+ filter: correlatedFilterSchema,
1604
+ args: correlatedCountArgsSchema,
1605
+ options: correlatedEmptyOptionsSchema
1606
+ }).strict();
1607
+ var includeItemSchema = import_zod2.z.union([
1608
+ legacyIncludeItemSchema,
1609
+ correlatedReadByIdSchema,
1610
+ correlatedReadByFilterSchema,
1611
+ correlatedListSchema,
1612
+ correlatedCountSchema
1613
+ ]);
1614
+ var correlatedNestedIncludeSchema = import_zod2.z.union([includeItemSchema, import_zod2.z.array(includeItemSchema)]);
1615
+ var rejectDuplicateCorrelatedPaths = (items, ctx) => {
1616
+ const seen = /* @__PURE__ */ new Set();
1617
+ items.forEach((item, index) => {
1618
+ if ((0, import_utils10.isPlainObject)(item) && item.mode === "correlated" && typeof item.path === "string") {
1619
+ if (seen.has(item.path)) {
1620
+ ctx.addIssue({
1621
+ code: import_zod2.z.ZodIssueCode.custom,
1622
+ message: `Duplicate correlated include output path: ${item.path}`,
1623
+ path: [index, "path"]
1624
+ });
1625
+ } else {
1626
+ seen.add(item.path);
1627
+ }
1628
+ }
1629
+ });
1630
+ };
1631
+ var includeSchema = import_zod2.z.union([
1632
+ includeItemSchema,
1633
+ import_zod2.z.array(includeItemSchema).superRefine(rejectDuplicateCorrelatedPaths)
1634
+ ]);
1282
1635
  var fieldsSchema = import_zod2.z.array(import_zod2.z.string().min(1));
1283
1636
  var taskSchema = import_zod2.z.object({
1284
1637
  type: import_zod2.z.string().min(1),
@@ -1395,8 +1748,9 @@ function defineRequestSchema(validator, options = {}) {
1395
1748
  };
1396
1749
  }
1397
1750
  function fromZod(schema) {
1398
- return (value) => {
1399
- const result = schema.safeParse(value);
1751
+ return async (value) => {
1752
+ const candidate = schema;
1753
+ const result = typeof candidate.safeParseAsync === "function" ? await candidate.safeParseAsync.call(schema, value) : schema.safeParse(value);
1400
1754
  if (result.success) {
1401
1755
  return {
1402
1756
  success: true,
@@ -1433,6 +1787,9 @@ function fromYup(schema) {
1433
1787
  data
1434
1788
  };
1435
1789
  } catch (error) {
1790
+ if (!isYupValidationError(error)) {
1791
+ throw error;
1792
+ }
1436
1793
  return {
1437
1794
  success: false,
1438
1795
  issues: normalizeYupIssues(error)
@@ -1457,8 +1814,32 @@ function fromJoi(schema) {
1457
1814
  }
1458
1815
  function fromAjv(validator) {
1459
1816
  return async (value) => {
1460
- const valid = await validator(value);
1461
- if (valid) {
1817
+ const outcome = validator(value);
1818
+ if (isThenable(outcome)) {
1819
+ try {
1820
+ const data = await outcome;
1821
+ if (data === false) {
1822
+ return {
1823
+ success: false,
1824
+ issues: normalizeAjvIssues(validator.errors ?? [])
1825
+ };
1826
+ }
1827
+ return {
1828
+ success: true,
1829
+ data: data === true ? value : data
1830
+ };
1831
+ } catch (error) {
1832
+ if (!isAjvValidationError(error)) {
1833
+ throw error;
1834
+ }
1835
+ return {
1836
+ success: false,
1837
+ issues: normalizeAjvIssues(error.errors ?? [])
1838
+ };
1839
+ }
1840
+ }
1841
+ const errorsSnapshot = validator.errors ? [...validator.errors] : [];
1842
+ if (outcome) {
1462
1843
  return {
1463
1844
  success: true,
1464
1845
  data: value
@@ -1466,7 +1847,7 @@ function fromAjv(validator) {
1466
1847
  }
1467
1848
  return {
1468
1849
  success: false,
1469
- issues: normalizeAjvIssues(validator.errors ?? [])
1850
+ issues: normalizeAjvIssues(errorsSnapshot)
1470
1851
  };
1471
1852
  };
1472
1853
  }
@@ -1487,6 +1868,25 @@ function fromValibot(schema, safeParse) {
1487
1868
  }
1488
1869
  function fromArkType(type) {
1489
1870
  return async (value) => {
1871
+ if (isStandardSchema(type)) {
1872
+ const result2 = await type["~standard"].validate(value);
1873
+ if (isArkTypeErrors(result2)) {
1874
+ return {
1875
+ success: false,
1876
+ issues: normalizeArkTypeIssues(result2)
1877
+ };
1878
+ }
1879
+ if (isStandardSchemaFailure(result2)) {
1880
+ return {
1881
+ success: false,
1882
+ issues: normalizeArkTypeStandardIssues(result2.issues)
1883
+ };
1884
+ }
1885
+ return {
1886
+ success: true,
1887
+ data: result2.value
1888
+ };
1889
+ }
1490
1890
  const result = await type(value);
1491
1891
  if (!isArkTypeErrors(result)) {
1492
1892
  return {
@@ -1539,6 +1939,9 @@ function fromVine(validator) {
1539
1939
  data: output
1540
1940
  };
1541
1941
  } catch (error) {
1942
+ if (!isVineValidationError(error)) {
1943
+ throw error;
1944
+ }
1542
1945
  return {
1543
1946
  success: false,
1544
1947
  issues: normalizeVineError(error)
@@ -1599,6 +2002,13 @@ function normalizeArkTypeIssues(issues) {
1599
2002
  }));
1600
2003
  return normalized.length ? normalized : [{ message: issues.summary ?? "Validation failed" }];
1601
2004
  }
2005
+ function normalizeArkTypeStandardIssues(issues) {
2006
+ const normalized = issues.map((issue) => ({
2007
+ message: issue.message ?? issue.problem ?? "Validation failed",
2008
+ path: issue.path?.flatMap((segment) => normalizePathSegment(segment))
2009
+ }));
2010
+ return normalized.length ? normalized : [{ message: "Validation failed" }];
2011
+ }
1602
2012
  function normalizeIoTsIssues(issues) {
1603
2013
  return issues.map((issue) => ({
1604
2014
  message: issue.message ?? "Validation failed",
@@ -1633,25 +2043,64 @@ function normalizeVineField(message) {
1633
2043
  }
1634
2044
  return path.length ? path : void 0;
1635
2045
  }
2046
+ function decodeAjvSegment(segment) {
2047
+ return segment.replace(/~1/g, "/").replace(/~0/g, "~");
2048
+ }
2049
+ function encodePointerSegment(segment) {
2050
+ return encodeURIComponent(segment.replace(/~/g, "~0").replace(/\//g, "~1"));
2051
+ }
1636
2052
  function parseAjvPath(issue) {
1637
- const path = issue.instancePath?.split("/").filter(Boolean).map((segment) => /^\d+$/.test(segment) ? Number(segment) : segment);
1638
- if (issue.params?.missingProperty) {
1639
- return (path ?? []).concat(issue.params.missingProperty);
2053
+ const raw = issue.instancePath;
2054
+ const base = raw == null || raw === "" ? [] : (raw.startsWith("/") ? raw.slice(1) : raw).split("/").map(decodeAjvSegment);
2055
+ const missing = issue.params?.missingProperty;
2056
+ if (typeof missing === "string") {
2057
+ return [...base, missing];
1640
2058
  }
1641
- return path;
2059
+ return base;
1642
2060
  }
1643
2061
  function parsePathString(path) {
1644
2062
  if (!path) return void 0;
1645
2063
  return path.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean).map((segment) => /^\d+$/.test(segment) ? Number(segment) : segment);
1646
2064
  }
1647
2065
  function isYupValidationError(error) {
1648
- return typeof error === "object" && error !== null && "message" in error;
2066
+ if (typeof error !== "object" || error === null || !("message" in error)) {
2067
+ return false;
2068
+ }
2069
+ const candidate = error;
2070
+ return candidate.name === "ValidationError" && typeof candidate.message === "string" && Array.isArray(candidate.errors) && Array.isArray(candidate.inner);
1649
2071
  }
1650
2072
  function isArkTypeErrors(value) {
1651
- return Array.isArray(value);
2073
+ if (typeof value !== "object" || value === null) {
2074
+ return false;
2075
+ }
2076
+ const record = value;
2077
+ return record.arkKind === "errors" || record[" arkKind"] === "errors";
2078
+ }
2079
+ function isThenable(value) {
2080
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
2081
+ }
2082
+ function isAjvValidationError(error) {
2083
+ if (typeof error !== "object" || error === null || !("errors" in error)) {
2084
+ return false;
2085
+ }
2086
+ const errors = error.errors;
2087
+ return errors == null || Array.isArray(errors);
1652
2088
  }
1653
2089
  function isVineValidationError(error) {
1654
- return typeof error === "object" && error !== null && "messages" in error && Array.isArray(error.messages);
2090
+ if (typeof error !== "object" || error === null || !("messages" in error)) {
2091
+ return false;
2092
+ }
2093
+ const candidate = error;
2094
+ if (!Array.isArray(candidate.messages) || candidate.messages.length === 0) {
2095
+ return false;
2096
+ }
2097
+ const hasDiscriminator = candidate.code === "E_VALIDATION_ERROR" || candidate.name === "ValidationError";
2098
+ if (!hasDiscriminator) {
2099
+ return false;
2100
+ }
2101
+ return candidate.messages.every(
2102
+ (message) => typeof message === "object" && message !== null && typeof message.message === "string" && typeof message.field === "string"
2103
+ );
1655
2104
  }
1656
2105
  function formatIssue(issue, key, location = "pointer") {
1657
2106
  const path = (issue.path ?? []).map(String);
@@ -1668,7 +2117,7 @@ function formatIssue(issue, key, location = "pointer") {
1668
2117
  };
1669
2118
  }
1670
2119
  function buildPointer(path) {
1671
- return path.length === 0 ? "#" : `#/${path.join("/")}`;
2120
+ return path.length === 0 ? "#" : `#/${path.map(encodePointerSegment).join("/")}`;
1672
2121
  }
1673
2122
 
1674
2123
  // src/validation/model-router.ts
@@ -1719,7 +2168,7 @@ var listBodySchema = import_zod3.z.object({
1719
2168
  }).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query"]));
1720
2169
  var countBodySchema = import_zod3.z.object({
1721
2170
  filter: objectOrArraySchema.optional()
1722
- }).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query", "access", "options"]));
2171
+ }).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query", "access", "options", "include"]));
1723
2172
  var readFilterBodySchema = import_zod3.z.object({
1724
2173
  filter: objectOrArraySchema.optional(),
1725
2174
  select: projectionSchema.optional(),