@sanity/validation 6.12.0 → 6.13.0-next.17

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.
@@ -1,10 +1,10 @@
1
1
  import { isArrayOfBlocksSchemaType, isKeyedObject, isReference, isSlug, isTypedObject } from "@sanity/types";
2
2
  import { createClientConcurrencyLimiter } from "@sanity/util/client";
3
3
  import { ConcurrencyLimiter } from "@sanity/util/concurrency-limiter";
4
+ import { dequal } from "dequal/lite";
4
5
  import flatten from "lodash-es/flatten.js";
5
- import isEqual from "lodash-es/isEqual.js";
6
- import { Observable, Subject, bufferTime, concat, defer, filter, finalize, firstValueFrom, from, lastValueFrom, map, merge, mergeMap, of, share, switchMap } from "rxjs";
7
- import { catchError, map as map$1, mergeAll, mergeMap as mergeMap$1, switchMap as switchMap$1, toArray } from "rxjs/operators";
6
+ import { Observable, Subject, bufferTime, concat, defer, filter, firstValueFrom, from, lastValueFrom, map, merge, mergeMap, of, share, takeUntil, throwError } from "rxjs";
7
+ import { catchError, map as map$1, mergeAll, mergeMap as mergeMap$1, switchMap, toArray } from "rxjs/operators";
8
8
  import { createInstance } from "i18next";
9
9
  import { Rule as Rule$1 } from "@sanity/schema";
10
10
  import get from "lodash-es/get.js";
@@ -59,7 +59,27 @@ const validationMarkerCodes = {
59
59
  valueNotAllowed: "value.not-allowed",
60
60
  valueRequired: "value.required",
61
61
  valueTypeMismatch: "value.type-mismatch"
62
- }, validationLocaleStrings = {
62
+ };
63
+ function abortSignalAsObservable(signal) {
64
+ return new Observable((subscriber) => {
65
+ let onAbort = () => subscriber.error(signal.reason);
66
+ if (signal.aborted) {
67
+ onAbort();
68
+ return;
69
+ }
70
+ return signal.addEventListener("abort", onAbort, { once: !0 }), () => signal.removeEventListener("abort", onAbort);
71
+ });
72
+ }
73
+ function cancelWith(signal) {
74
+ return (source) => signal ? source.pipe(takeUntil(abortSignalAsObservable(signal))) : source;
75
+ }
76
+ var ClientUnavailableError = class extends Error {
77
+ name = "ClientUnavailableError";
78
+ constructor() {
79
+ super("A Sanity client is required to run this validation check");
80
+ }
81
+ };
82
+ const validationLocaleStrings = {
63
83
  "array.exact-length": "Must have exactly {{wantedLength}} items",
64
84
  "array.exact-length_blocks": "Must have exactly {{wantedLength}} blocks",
65
85
  "array.item-duplicate": "Can't be a duplicate",
@@ -123,6 +143,13 @@ function getFallbackLocaleSource() {
123
143
  t: i18n.t
124
144
  }, fallbackLocaleSource;
125
145
  }
146
+ const internalValidators = /* @__PURE__ */ new WeakSet();
147
+ function markInternalValidator(validator) {
148
+ return internalValidators.add(validator), validator;
149
+ }
150
+ function isInternalValidator(validator) {
151
+ return typeof validator == "function" && internalValidators.has(validator);
152
+ }
126
153
  function resolveConditionalProperty(property, context) {
127
154
  let { currentUser, document, parent, value, path } = context;
128
155
  return typeof property == "boolean" || property === void 0 ? !!property : property({
@@ -133,29 +160,27 @@ function resolveConditionalProperty(property, context) {
133
160
  path
134
161
  }) === !0;
135
162
  }
136
- function createBatchedGetDocumentExists(client) {
137
- let id$ = new Subject(), limiter = new ConcurrencyLimiter(1), existence$ = id$.pipe(bufferTime(250, null, 100), map((ids) => Array.from(new Set(ids))), mergeMap((ids) => from(limiter.ready()).pipe(switchMap(() => client.observable.request({
163
+ function createBatchedGetDocumentExists(client, defaultSignal) {
164
+ let id$ = new Subject(), existence$ = id$.pipe(bufferTime(250, null, 100), map((ids) => Array.from(new Set(ids))), filter((ids) => ids.length > 0), mergeMap((ids) => client.observable.request({
138
165
  url: client.getDataUrl("doc", ids.join(",")),
139
166
  query: { excludeContent: "true" },
167
+ signal: defaultSignal,
140
168
  tag: "documents-availability"
141
169
  }).pipe(map((availability) => ({
142
170
  availability,
143
171
  ids
144
- })))), finalize(limiter.release))), mergeMap(({ availability, ids }) => ids.map((id) => {
145
- let omittedIds = availability.omitted.reduce((acc, next) => (acc[next.id] = next.reason, acc), {});
146
- return omittedIds[id] && omittedIds[id] === "existence" ? {
147
- id,
148
- exists: !1
149
- } : {
172
+ }))), 1), mergeMap(({ availability, ids }) => {
173
+ let missingIds = new Set(availability.omitted.filter(({ reason }) => reason === "existence").map(({ id }) => id));
174
+ return ids.map((id) => ({
150
175
  id,
151
- exists: !0
152
- };
153
- })), share());
176
+ exists: !missingIds.has(id)
177
+ }));
178
+ }), share());
154
179
  return async function getDocumentExists(options) {
155
- let result = firstValueFrom(existence$.pipe(filter(({ id }) => id === options.id)));
156
- id$.next(options.id);
157
- let { exists } = await result;
158
- return exists;
180
+ let signal = options.signal || defaultSignal;
181
+ signal?.throwIfAborted();
182
+ let result = firstValueFrom(existence$.pipe(filter(({ id }) => id === options.id), map(({ exists }) => exists), cancelWith(signal)));
183
+ return id$.next(options.id), result;
159
184
  };
160
185
  }
161
186
  function pathToString(path = []) {
@@ -573,7 +598,10 @@ const dateValidators = {
573
598
  if ("weak" in type && type.weak) return !0;
574
599
  if (!getDocumentExists) throw Error("`getDocumentExists` was not provided in validation context");
575
600
  let documentId = document?._id;
576
- return documentId && value._ref == getPublishedId(documentId) || await getDocumentExists({ id: value._ref }) ? !0 : {
601
+ return documentId && value._ref == getPublishedId(documentId) || await getDocumentExists({
602
+ id: value._ref,
603
+ signal: context.signal
604
+ }) ? !0 : {
577
605
  code: validationMarkerCodes.referenceNotPublished,
578
606
  details: { referenceId: value._ref },
579
607
  message: i18n.t("validation:object.reference-not-published", { documentId: value._ref })
@@ -599,7 +627,7 @@ const dateValidators = {
599
627
  let [type, libraryId, documentId] = value.media._ref.split(":", 3), resourceConfig = { resource: {
600
628
  type,
601
629
  id: libraryId
602
- } }, asset = await context.getClient({ apiVersion: "2025-02-19" }).withConfig(resourceConfig).fetch("*[_id == $id] { ..., 'currentVersion': @.currentVersion-> { ... } }[0]", { id: documentId });
630
+ } }, asset = await context.getClient({ apiVersion: "2025-02-19" }).withConfig(resourceConfig).fetch("*[_id == $id] { ..., 'currentVersion': @.currentVersion-> { ... } }[0]", { id: documentId }, { signal: context.signal });
603
631
  if (!asset) return console.warn(`${context.i18n.t("validation:object.media-not-found")}\nAsset ID: ${value.media._ref}`), {
604
632
  code: validationMarkerCodes.mediaNotFound,
605
633
  details: { referenceId: value.media._ref },
@@ -740,8 +768,8 @@ const dateValidators = {
740
768
  let rule = new Rule();
741
769
  return rule._type = this._type, rule._message = this._message, rule._required = this._required, rule._rules = [...this._rules], rule._level = this._level, rule._fieldRules = this._fieldRules, rule._typeDef = this._typeDef, rule;
742
770
  }
743
- async validate(value, { __internal = {}, ...context }) {
744
- let { customValidationConcurrencyLimiter } = __internal, valueIsEmpty = value == null;
771
+ async validate(value, options) {
772
+ let { __internal = {}, ...context } = options, { customValidation = !0, customValidationConcurrencyLimiter, markIncomplete } = __internal, valueIsEmpty = value == null;
745
773
  if (valueIsEmpty && this._required === "optional") return EMPTY_ARRAY;
746
774
  let rules = this._required === void 0 && valueIsEmpty ? this._rules.filter((curr) => curr.flag === "custom") : this._rules, validators = this._type && typeValidators[this._type] || genericValidators;
747
775
  return (await Promise.all(rules.map(async (curr) => {
@@ -752,21 +780,16 @@ const dateValidators = {
752
780
  throw Error(`Validator for flag "${curr.flag}" not found for ${forType}`);
753
781
  }
754
782
  let specConstraint = "constraint" in curr ? curr.constraint : null;
755
- if (isFieldRef(specConstraint) && (specConstraint = get(context.parent, specConstraint.path)), curr.flag === "custom" && customValidationConcurrencyLimiter && !specConstraint?.bypassConcurrencyLimit) {
783
+ if (isFieldRef(specConstraint) && (specConstraint = get(context.parent, specConstraint.path)), (curr.flag === "custom" || curr.flag === "media") && !isInternalValidator(specConstraint) && !customValidation) return markIncomplete?.(), [];
784
+ if (curr.flag === "custom" && customValidationConcurrencyLimiter && !specConstraint?.bypassConcurrencyLimit) {
756
785
  let customValidator = specConstraint;
757
- specConstraint = async (...args) => {
758
- await customValidationConcurrencyLimiter.ready();
759
- try {
760
- return await customValidator(...args);
761
- } finally {
762
- customValidationConcurrencyLimiter.release();
763
- }
764
- };
786
+ specConstraint = (...args) => customValidationConcurrencyLimiter.run(() => customValidator(...args), context.signal);
765
787
  }
766
788
  let message = isLocalizedMessages(this._message) ? localizeMessage(this._message, context.i18n) : this._message;
767
789
  try {
768
790
  return convertToValidationMarker(await validator(specConstraint, value, message, context), this._level, context, { code: fallbackCodeForRule(curr.flag) });
769
791
  } catch (err) {
792
+ if (context.signal?.throwIfAborted(), err instanceof ClientUnavailableError) return markIncomplete?.(), [];
770
793
  let errorMessage = `${pathToString(context.path)}: Exception occurred while validating value: ${err.message}`;
771
794
  return convertToValidationMarker({
772
795
  code: validationMarkerCodes.validationException,
@@ -776,6 +799,9 @@ const dateValidators = {
776
799
  }))).flat();
777
800
  }
778
801
  }, memoizedWarnOnArraySlug = memoize(warnOnArraySlug);
802
+ function hasCustomSlugUniqueness(options) {
803
+ return typeof options == "object" && !!options && "isUnique" in options && typeof options.isUnique == "function";
804
+ }
779
805
  function serializePath(path) {
780
806
  return path.reduce((target, part, i) => {
781
807
  let isIndex = typeof part == "number", isKey = isKeyedObject(part);
@@ -797,7 +823,10 @@ const defaultIsUnique = (slug, context) => {
797
823
  docType,
798
824
  published: getPublishedId(document._id),
799
825
  slug
800
- }, { tag: "validation.slug-is-unique" });
826
+ }, {
827
+ signal: context.signal,
828
+ tag: "validation.slug-is-unique"
829
+ });
801
830
  };
802
831
  function warnOnArraySlug(serializedPath) {
803
832
  console.warn([
@@ -806,25 +835,21 @@ function warnOnArraySlug(serializedPath) {
806
835
  "To disable this message, set `disableArrayWarning: true` on the slug `options` field"
807
836
  ].join("\n"));
808
837
  }
809
- /**
810
- * Validates slugs values by querying for uniqueness from the client.
811
- *
812
- * This is a custom rule implementation (e.g. `Rule.custom(slugValidator)`)
813
- * that's populated in `inferFromSchemaType` when the type name is `slug`
814
- */
815
- const slugValidator = async (value, context) => {
838
+ const slugStructureValidator = (value, context) => {
816
839
  if (!value) return !0;
817
840
  let { i18n } = context;
818
- if (typeof value != "object" || Array.isArray(value)) return {
841
+ return typeof value != "object" || Array.isArray(value) ? {
819
842
  code: validationMarkerCodes.slugInvalidType,
820
843
  details: { actualType: typeString(value) },
821
844
  message: i18n.t("validation:slug.not-object")
822
- };
823
- if (!isSlug(value) || value.current.trim().length === 0) return {
845
+ } : !isSlug(value) || value.current.trim().length === 0 ? {
824
846
  code: validationMarkerCodes.slugMissingCurrent,
825
847
  message: i18n.t("validation:slug.missing-current")
826
- };
827
- let isUnique = context?.type?.options?.isUnique || defaultIsUnique, slugContext = {
848
+ } : !0;
849
+ };
850
+ async function validateSlugUniqueness(value, context, isUnique) {
851
+ if (!isSlug(value) || value.current.trim().length === 0) return !0;
852
+ let { i18n } = context, slugContext = {
828
853
  ...context,
829
854
  parent: context.parent,
830
855
  type: context.type,
@@ -835,6 +860,10 @@ const slugValidator = async (value, context) => {
835
860
  details: { slug: value.current },
836
861
  message: i18n.t("validation:slug.not-unique", { slug: value.current })
837
862
  };
863
+ }
864
+ const defaultSlugUniquenessValidator = (value, context) => validateSlugUniqueness(value, context, defaultIsUnique), customSlugUniquenessValidator = (value, context) => {
865
+ let options = context.type?.options;
866
+ return !hasCustomSlugUniqueness(options) || validateSlugUniqueness(value, context, options.isUnique);
838
867
  }, ruleConstraintTypes = {
839
868
  array: !0,
840
869
  boolean: !0,
@@ -850,7 +879,13 @@ function baseRuleReducer(inputRule, type) {
850
879
  let baseRule = inputRule;
851
880
  isRuleConstraint(type.jsonType) && (baseRule = baseRule.type(type.jsonType));
852
881
  let typeOptionsList = type?.options && typeof type.options == "object" && "list" in type.options && type.options.list;
853
- return Array.isArray(typeOptionsList) && (baseRule = baseRule.valid(typeOptionsList.map((option) => extractValueFromListOption(option, type)))), type.name === "datetime" || type.name === "date" ? baseRule.type("Date") : type.name === "url" ? baseRule.uri() : type.name === "slug" ? baseRule.custom(slugValidator, { bypassConcurrencyLimit: !0 }) : type.name === "reference" ? baseRule.reference() : type.name === "email" ? baseRule.email() : baseRule;
882
+ if (Array.isArray(typeOptionsList) && (baseRule = baseRule.valid(typeOptionsList.map((option) => extractValueFromListOption(option, type)))), type.name === "datetime" || type.name === "date") return baseRule.type("Date");
883
+ if (type.name === "url") return baseRule.uri();
884
+ if (type.name === "slug") {
885
+ let uniquenessValidator = hasCustomSlugUniqueness(type.options) ? customSlugUniquenessValidator : markInternalValidator(defaultSlugUniquenessValidator);
886
+ return baseRule.custom(markInternalValidator(slugStructureValidator), { bypassConcurrencyLimit: !0 }).custom(uniquenessValidator, { bypassConcurrencyLimit: !0 });
887
+ }
888
+ return type.name === "reference" ? baseRule.reference() : type.name === "email" ? baseRule.email() : baseRule;
854
889
  }
855
890
  function hasValueField(typeDef) {
856
891
  return typeDef ? !("fields" in typeDef) && typeDef.type ? hasValueField(typeDef.type) : !("fields" in typeDef) || !Array.isArray(typeDef.fields) ? !1 : typeDef.fields.some((field) => field.name === "value") : !1;
@@ -861,7 +896,7 @@ function extractValueFromListOption(option, typeDef) {
861
896
  const isUriSpec = (spec) => spec.flag === "uri";
862
897
  function omitLeakedDefaultUri(rules, typeDef) {
863
898
  if (!getTypeChain(typeDef).some((type) => type.name === "url")) return rules;
864
- let defaultUri = new Rule$2(typeDef).uri()._rules.find(isUriSpec)?.constraint, isDefaultUri = (spec) => isUriSpec(spec) && isEqual(spec.constraint, defaultUri), isCustomUri = (spec) => isUriSpec(spec) && !isEqual(spec.constraint, defaultUri);
899
+ let defaultUri = new Rule$2(typeDef).uri()._rules.find(isUriSpec)?.constraint, isDefaultUri = (spec) => isUriSpec(spec) && dequal(spec.constraint, defaultUri), isCustomUri = (spec) => isUriSpec(spec) && !dequal(spec.constraint, defaultUri);
865
900
  return rules.some((rule) => rule._rules.some(isCustomUri)) ? rules.map((rule) => {
866
901
  if (!rule._rules.some(isDefaultUri)) return rule;
867
902
  let cleaned = rule.clone();
@@ -912,6 +947,9 @@ const requestIdleCallbackShim = function requestIdleCallbackShim(callback, _opti
912
947
  path: [unknownField]
913
948
  }));
914
949
  }, DEFAULT_VALIDATION_CLIENT_OPTIONS = { apiVersion: "2025-02-19" }, isRecord = (maybeRecord) => typeof maybeRecord == "object" && !!maybeRecord && !Array.isArray(maybeRecord);
950
+ function throwClientUnavailable() {
951
+ throw new ClientUnavailableError();
952
+ }
915
953
  /**
916
954
  * Recursively extracts all `_fieldRules` from a rule and its nested constraints.
917
955
  * This handles cases where `Rule.fields()` is used inside `Rule.all()` or `Rule.either()`.
@@ -939,9 +977,10 @@ function resolveTypeForArrayItem(item, candidates) {
939
977
  * @beta
940
978
  * @deprecated Prefer {@link validateDocument} with `{document, schema, client}` for new code.
941
979
  */
942
- function validateDocumentWithWorkspace({ document, workspace, getClient = workspace.getClient, getDocumentExists, environment = "studio", maxCustomValidationConcurrency, maxFetchConcurrency, currentUser }) {
980
+ function validateDocumentWithWorkspace({ document, workspace, getClient = workspace.getClient, getDocumentExists, environment = "studio", maxCustomValidationConcurrency, maxFetchConcurrency, currentUser, customValidation, signal }) {
943
981
  return validateDocumentInternal({
944
982
  currentUser,
983
+ customValidation,
945
984
  document,
946
985
  environment,
947
986
  getClient,
@@ -949,33 +988,58 @@ function validateDocumentWithWorkspace({ document, workspace, getClient = worksp
949
988
  i18n: workspace.i18n,
950
989
  maxCustomValidationConcurrency,
951
990
  maxFetchConcurrency,
952
- schema: workspace.schema
991
+ schema: workspace.schema,
992
+ signal
953
993
  });
954
994
  }
995
+ /**
996
+ * Validates a document against a compiled schema. Returns failures and whether
997
+ * validation completed
998
+ * without deciding whether the document may be edited or published.
999
+ *
1000
+ * @beta
1001
+ */
955
1002
  function validateDocument(options) {
956
- if ("workspace" in options) return validateDocumentWithWorkspace(options);
957
- let { client, document, schema, ...internalOptions } = options;
958
- return validateDocumentInternal({
1003
+ let { client, customValidation = !!options.client, document, getDocumentExists, schema, ...internalOptions } = options;
1004
+ return evaluateDocumentInternal({
959
1005
  ...internalOptions,
1006
+ customValidation,
960
1007
  document,
961
1008
  environment: "cli",
962
- getClient: ({ apiVersion }) => client.withConfig({ apiVersion }),
1009
+ getClient: client ? ({ apiVersion }) => client.withConfig({ apiVersion }) : throwClientUnavailable,
1010
+ getDocumentExists: getDocumentExists || (client ? void 0 : throwClientUnavailable),
963
1011
  i18n: getFallbackLocaleSource(),
964
1012
  schema
965
1013
  });
966
1014
  }
1015
+ function createDocumentValidationResult(markers, complete) {
1016
+ return {
1017
+ status: getDocumentValidationStatus(markers, complete),
1018
+ markers: markers.map(toDocumentValidationMarker)
1019
+ };
1020
+ }
1021
+ function getDocumentValidationStatus(markers, complete) {
1022
+ return markers.length > 0 ? "failed" : complete ? "passed" : "notEvaluated";
1023
+ }
1024
+ /** @internal */
1025
+ function validateDocumentInternal(options) {
1026
+ return evaluateDocumentInternal(options).then(({ markers }) => markers);
1027
+ }
967
1028
  /** @internal */
968
- function validateDocumentInternal({ document, schema, getClient, getDocumentExists, i18n = getFallbackLocaleSource(), environment, maxCustomValidationConcurrency, maxFetchConcurrency, currentUser }) {
969
- let limitConcurrency = createClientConcurrencyLimiter(maxFetchConcurrency ?? 25), getConcurrencyLimitedClient = (clientOptions) => limitConcurrency(getClient(clientOptions));
970
- return lastValueFrom(validateDocumentObservable({
1029
+ function evaluateDocumentInternal({ document, schema, getClient, getDocumentExists, i18n = getFallbackLocaleSource(), environment, maxCustomValidationConcurrency, maxFetchConcurrency, currentUser, customValidation = !0, signal }) {
1030
+ if (signal?.aborted) return Promise.reject(signal.reason);
1031
+ let limitConcurrency = createClientConcurrencyLimiter(maxFetchConcurrency ?? 25, signal), getConcurrencyLimitedClient = (clientOptions) => limitConcurrency(getClient(clientOptions));
1032
+ return lastValueFrom(evaluateDocumentObservable({
971
1033
  document,
972
1034
  getClient: getConcurrencyLimitedClient,
973
1035
  i18n,
974
1036
  schema,
975
- getDocumentExists: getDocumentExists || createBatchedGetDocumentExists(getClient(DEFAULT_VALIDATION_CLIENT_OPTIONS)),
1037
+ getDocumentExists: getDocumentExists || createBatchedGetDocumentExists(getClient(DEFAULT_VALIDATION_CLIENT_OPTIONS), signal),
976
1038
  environment,
977
1039
  maxCustomValidationConcurrency,
978
- currentUser
1040
+ currentUser,
1041
+ customValidation,
1042
+ signal
979
1043
  }));
980
1044
  }
981
1045
  const customValidationConcurrencyLimiters = /* @__PURE__ */ new WeakMap();
@@ -983,48 +1047,69 @@ const customValidationConcurrencyLimiters = /* @__PURE__ */ new WeakMap();
983
1047
  * Validates a document against the given schema, returning an Observable
984
1048
  * @internal
985
1049
  */
986
- function validateDocumentObservable({ document, getClient, i18n = getFallbackLocaleSource(), schema, getDocumentExists, environment, maxCustomValidationConcurrency, currentUser }) {
1050
+ function validateDocumentObservable(options) {
1051
+ return evaluateDocumentObservable(options).pipe(map$1(({ markers }) => markers));
1052
+ }
1053
+ /**
1054
+ * Validates a document against the given schema, including completion status.
1055
+ * @internal
1056
+ */
1057
+ function evaluateDocumentObservable(options) {
1058
+ let { signal } = options;
1059
+ return defer(() => (signal?.throwIfAborted(), evaluateDocumentObservableWithoutCancellation(options))).pipe(cancelWith(signal));
1060
+ }
1061
+ function evaluateDocumentObservableWithoutCancellation({ document, getClient, i18n = getFallbackLocaleSource(), schema, getDocumentExists, environment, maxCustomValidationConcurrency, currentUser, customValidation = !0, signal }) {
987
1062
  if (typeof document?._type != "string") throw Error("Tried to validate a value without a '_type'");
988
1063
  let documentType = schema.get(document._type);
989
- if (!documentType) return environment === "studio" ? (console.warn("Schema type for object type \"%s\" not found, skipping validation", document._type), of([])) : of([{
1064
+ if (!documentType) return environment === "studio" ? (console.warn("Schema type for object type \"%s\" not found, skipping validation", document._type), of(createDocumentValidationResult([], !0))) : of(createDocumentValidationResult([{
990
1065
  code: validationMarkerCodes.documentUnknownType,
991
1066
  details: { documentType: document._type },
992
1067
  level: "warning",
993
1068
  message: `Could not find schema type for type '${document._type}', skipping validation`,
994
1069
  path: []
995
- }]);
1070
+ }], !0));
996
1071
  let customValidationConcurrencyLimiter = customValidationConcurrencyLimiters.get(schema);
997
- customValidationConcurrencyLimiter || (customValidationConcurrencyLimiter = new ConcurrencyLimiter(maxCustomValidationConcurrency ?? 5), customValidationConcurrencyLimiters.set(schema, customValidationConcurrencyLimiter));
998
- let validationOptions = {
999
- getClient,
1000
- schema,
1001
- parent: void 0,
1002
- value: document,
1003
- path: [],
1004
- document,
1005
- type: documentType,
1006
- i18n,
1007
- getDocumentExists,
1008
- environment,
1009
- customValidationConcurrencyLimiter,
1010
- currentUser
1011
- };
1012
- return from(i18n.loadNamespaces(["validation"])).pipe(switchMap$1(() => validateItemObservable(validationOptions)), map$1((markers) => markers.map(toDocumentValidationMarker)), catchError((err) => {
1013
- console.error(err);
1014
- let message = err?.message || "Unknown error", errorMarker = {
1015
- code: validationMarkerCodes.validationException,
1016
- level: "error",
1017
- message,
1018
- item: { message },
1019
- path: []
1072
+ return customValidationConcurrencyLimiter || (customValidationConcurrencyLimiter = new ConcurrencyLimiter(maxCustomValidationConcurrency ?? 5), customValidationConcurrencyLimiters.set(schema, customValidationConcurrencyLimiter)), defer(() => {
1073
+ let complete = !0, validationOptions = {
1074
+ getClient,
1075
+ schema,
1076
+ parent: void 0,
1077
+ value: document,
1078
+ path: [],
1079
+ document,
1080
+ type: documentType,
1081
+ i18n,
1082
+ getDocumentExists,
1083
+ environment,
1084
+ customValidationConcurrencyLimiter,
1085
+ currentUser,
1086
+ customValidation,
1087
+ signal,
1088
+ __internal: { markIncomplete: () => {
1089
+ complete = !1;
1090
+ } }
1020
1091
  };
1021
- return of([errorMarker]);
1022
- }));
1092
+ return from(i18n.loadNamespaces(["validation"])).pipe(switchMap(() => validateItemObservable(validationOptions)), map$1((markers) => createDocumentValidationResult(markers, complete)), catchError((err) => {
1093
+ if (signal?.aborted) return throwError(() => signal.reason);
1094
+ console.error(err);
1095
+ let message = err?.message || "Unknown error", errorMarker = {
1096
+ code: validationMarkerCodes.validationException,
1097
+ level: "error",
1098
+ message,
1099
+ item: { message },
1100
+ path: []
1101
+ };
1102
+ return of(createDocumentValidationResult([errorMarker], complete));
1103
+ }));
1104
+ });
1023
1105
  }
1024
1106
  function validateItem(opts) {
1025
- return lastValueFrom(validateItemObservable(opts));
1107
+ return lastValueFrom(defer(() => (opts.signal?.throwIfAborted(), validateItemObservable(opts))).pipe(cancelWith(opts.signal)));
1108
+ }
1109
+ function validateRule(rule, value, context) {
1110
+ return rule.validate(value, context);
1026
1111
  }
1027
- function validateItemObservable({ value, type, path = [], parent, customValidationConcurrencyLimiter, environment, ...restOfContext }) {
1112
+ function validateItemObservable({ value, type, path = [], parent, customValidationConcurrencyLimiter, environment, customValidation = !0, __internal, ...restOfContext }) {
1028
1113
  let ancestorHidden = restOfContext.hidden === !0, resolveHiddenForType = (schemaType, schemaValue, schemaParent, schemaPath, ancestorHiddenValue) => schemaType ? ancestorHiddenValue || resolveConditionalProperty(schemaType.hidden, {
1029
1114
  ...restOfContext,
1030
1115
  parent: schemaParent,
@@ -1036,21 +1121,25 @@ function validateItemObservable({ value, type, path = [], parent, customValidati
1036
1121
  "document",
1037
1122
  "file",
1038
1123
  "image"
1039
- ].includes(t.name)) && environment !== "studio" ? rule.custom(unknownFieldsValidator(type), { bypassConcurrencyLimit: !0 }).warning() : rule, rules = normalizeValidationRules(type, {
1124
+ ].includes(t.name)) && environment !== "studio" ? rule.custom(markInternalValidator(unknownFieldsValidator(type)), { bypassConcurrencyLimit: !0 }).warning() : rule, rules = normalizeValidationRules(type, {
1040
1125
  ...restOfContext,
1041
1126
  hidden,
1042
1127
  environment,
1043
1128
  parent,
1044
1129
  path,
1045
1130
  type
1046
- }), selfChecks = rules.map(addUnknownFieldsValidator).map((rule) => defer(() => rule.validate(value, {
1131
+ }), selfChecks = rules.map(addUnknownFieldsValidator).map((rule) => defer(() => validateRule(rule, value, {
1047
1132
  ...restOfContext,
1048
1133
  environment,
1049
1134
  hidden,
1050
1135
  parent,
1051
1136
  path,
1052
1137
  type,
1053
- __internal: { customValidationConcurrencyLimiter }
1138
+ __internal: {
1139
+ ...__internal,
1140
+ customValidation,
1141
+ customValidationConcurrencyLimiter
1142
+ }
1054
1143
  }))), nestedChecks = [], selfIsRequired = rules.some((rule) => rule.isRequired());
1055
1144
  if (type?.jsonType === "object" && (value || value == null && selfIsRequired)) {
1056
1145
  let fieldTypes = type.fields.reduce((acc, field) => (acc[field.name] = field.type, acc), {});
@@ -1061,14 +1150,18 @@ function validateItemObservable({ value, type, path = [], parent, customValidati
1061
1150
  validation
1062
1151
  }).map(addUnknownFieldsValidator).map((subRule) => {
1063
1152
  let nestedValue = isRecord(value) ? value[name] : void 0, nestedHidden = resolveHiddenForType(fieldType, nestedValue, value, path.concat(name), hidden);
1064
- return defer(() => subRule.validate(nestedValue, {
1153
+ return defer(() => validateRule(subRule, nestedValue, {
1065
1154
  ...restOfContext,
1066
1155
  parent: value,
1067
1156
  path: path.concat(name),
1068
1157
  type: fieldType,
1069
1158
  environment,
1070
1159
  hidden: nestedHidden,
1071
- __internal: { customValidationConcurrencyLimiter }
1160
+ __internal: {
1161
+ ...__internal,
1162
+ customValidation,
1163
+ customValidationConcurrencyLimiter
1164
+ }
1072
1165
  }));
1073
1166
  });
1074
1167
  })), nestedChecks = nestedChecks.concat(type.fields.map((field) => validateItemObservable({
@@ -1079,7 +1172,9 @@ function validateItemObservable({ value, type, path = [], parent, customValidati
1079
1172
  path: path.concat(field.name),
1080
1173
  type: field.type,
1081
1174
  environment,
1082
- customValidationConcurrencyLimiter
1175
+ customValidationConcurrencyLimiter,
1176
+ customValidation,
1177
+ __internal
1083
1178
  })));
1084
1179
  }
1085
1180
  return type?.jsonType === "array" && Array.isArray(value) && (nestedChecks = nestedChecks.concat(value.map((item, index) => validateItemObservable({
@@ -1090,7 +1185,9 @@ function validateItemObservable({ value, type, path = [], parent, customValidati
1090
1185
  path: path.concat(isKeyedObject(item) ? { _key: item._key } : index),
1091
1186
  type: resolveTypeForArrayItem(item, type.of),
1092
1187
  environment,
1093
- customValidationConcurrencyLimiter
1188
+ customValidationConcurrencyLimiter,
1189
+ customValidation,
1190
+ __internal
1094
1191
  })))), defer(() => merge([...selfChecks, ...nestedChecks])).pipe(mergeMap$1((validateNode) => concat(idle(), validateNode), 40), mergeAll(), toArray(), map$1(flatten), map$1((results) => rules.some((rule) => extractFieldRulesFromRule(rule).length > 0) ? deduplicateMarkers(results) : results));
1095
1192
  }
1096
1193
  function deduplicateMarkers(markers) {
@@ -1102,7 +1199,7 @@ function deduplicateMarkers(markers) {
1102
1199
  marker.message,
1103
1200
  marker.path
1104
1201
  ]), bucket = buckets.get(key);
1105
- return bucket ? !bucket.some((existing) => isEqual(existing, marker)) && (bucket.push(marker), !0) : (buckets.set(key, [marker]), !0);
1202
+ return bucket ? !bucket.some((existing) => dequal(existing, marker)) && (bucket.push(marker), !0) : (buckets.set(key, [marker]), !0);
1106
1203
  });
1107
1204
  }
1108
1205
  function hasValidationMarkerCode(marker) {
@@ -1122,6 +1219,6 @@ function idle(timeout) {
1122
1219
  return () => cancelIdleCallback(handle);
1123
1220
  });
1124
1221
  }
1125
- export { validateDocumentWithWorkspace as a, normalizeValidationRules as c, convertToValidationMarker as d, pathToString as f, validationMarkerCodes as h, validateDocumentObservable as i, Rule$2 as l, validationLocaleStrings as m, validateDocument as n, validateItem as o, getFallbackLocaleSource as p, validateDocumentInternal as r, getTypeChain as s, resolveTypeForArrayItem as t, typeString as u };
1222
+ export { validationMarkerCodes as _, validateDocumentInternal as a, validateItem as c, Rule$2 as d, typeString as f, validationLocaleStrings as g, getFallbackLocaleSource as h, validateDocument as i, getTypeChain as l, pathToString as m, evaluateDocumentObservable as n, validateDocumentObservable as o, convertToValidationMarker as p, resolveTypeForArrayItem as r, validateDocumentWithWorkspace as s, evaluateDocumentInternal as t, normalizeValidationRules as u };
1126
1223
 
1127
- //# sourceMappingURL=validateDocument-Cq33kUmN.js.map
1224
+ //# sourceMappingURL=validateDocument-DtBbNOGc.js.map