@sanity/validation 3.14.3 → 6.12.0-next.112

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.
Files changed (40) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +52 -0
  3. package/lib/_internal.d.ts +96 -0
  4. package/lib/_internal.js +39 -0
  5. package/lib/_internal.js.map +1 -0
  6. package/lib/index.d.ts +2 -0
  7. package/lib/index.js +2 -1306
  8. package/lib/validateDocument-6pLTIHIn.d.ts +277 -0
  9. package/lib/validateDocument-Cq33kUmN.js +1127 -0
  10. package/lib/validateDocument-Cq33kUmN.js.map +1 -0
  11. package/package.json +53 -45
  12. package/lib/dts/src/index.d.ts +0 -50
  13. package/lib/index.cjs.mjs +0 -9
  14. package/lib/index.esm.js +0 -1286
  15. package/lib/index.esm.js.map +0 -1
  16. package/lib/index.js.map +0 -1
  17. package/src/Rule.ts +0 -424
  18. package/src/ValidationError.ts +0 -32
  19. package/src/index.ts +0 -9
  20. package/src/inferFromSchema.ts +0 -19
  21. package/src/inferFromSchemaType.ts +0 -50
  22. package/src/util/convertToValidationMarker.ts +0 -84
  23. package/src/util/deepEquals.ts +0 -77
  24. package/src/util/escapeRegex.ts +0 -5
  25. package/src/util/normalizeValidationRules.test.ts +0 -170
  26. package/src/util/normalizeValidationRules.ts +0 -118
  27. package/src/util/pathToString.ts +0 -21
  28. package/src/util/requestIdleCallback.ts +0 -31
  29. package/src/util/typeString.test.ts +0 -27
  30. package/src/util/typeString.ts +0 -23
  31. package/src/validateDocument.test.ts +0 -703
  32. package/src/validateDocument.ts +0 -240
  33. package/src/validators/arrayValidator.ts +0 -100
  34. package/src/validators/booleanValidator.ts +0 -16
  35. package/src/validators/dateValidator.ts +0 -113
  36. package/src/validators/genericValidator.ts +0 -117
  37. package/src/validators/numberValidator.ts +0 -66
  38. package/src/validators/objectValidator.ts +0 -64
  39. package/src/validators/slugValidator.ts +0 -117
  40. package/src/validators/stringValidator.ts +0 -120
@@ -0,0 +1,1127 @@
1
+ import { isArrayOfBlocksSchemaType, isKeyedObject, isReference, isSlug, isTypedObject } from "@sanity/types";
2
+ import { createClientConcurrencyLimiter } from "@sanity/util/client";
3
+ import { ConcurrencyLimiter } from "@sanity/util/concurrency-limiter";
4
+ 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";
8
+ import { createInstance } from "i18next";
9
+ import { Rule as Rule$1 } from "@sanity/schema";
10
+ import get from "lodash-es/get.js";
11
+ import isPlainObject from "lodash-es/isPlainObject.js";
12
+ import * as legacyDateFormat from "@sanity/util/legacyDateFormat";
13
+ import { getPublishedId } from "@sanity/id-utils";
14
+ import memoize from "lodash-es/memoize.js";
15
+ /** Machine-readable codes emitted by built-in document validation. @beta */
16
+ const validationMarkerCodes = {
17
+ arrayDuplicateItem: "array.duplicate-item",
18
+ arrayExactLength: "array.exact-length",
19
+ arrayMaximumLength: "array.maximum-length",
20
+ arrayMinimumLength: "array.minimum-length",
21
+ assetRequired: "asset.required",
22
+ custom: "custom",
23
+ dateInvalidFormat: "date.invalid-format",
24
+ dateMaximum: "date.maximum",
25
+ dateMinimum: "date.minimum",
26
+ documentUnknownType: "document.unknown-type",
27
+ mediaCustom: "media.custom",
28
+ mediaInvalidReference: "media.invalid-reference",
29
+ mediaNotFound: "media.not-found",
30
+ numberGreaterThan: "number.greater-than",
31
+ numberInteger: "number.integer",
32
+ numberLessThan: "number.less-than",
33
+ numberMaximum: "number.maximum",
34
+ numberMinimum: "number.minimum",
35
+ numberPrecision: "number.precision",
36
+ objectUnknownField: "object.unknown-field",
37
+ referenceInvalid: "reference.invalid",
38
+ referenceNotPublished: "reference.not-published",
39
+ ruleAllFailed: "rule.all-failed",
40
+ ruleEitherFailed: "rule.either-failed",
41
+ slugInvalidType: "slug.invalid-type",
42
+ slugMissingCurrent: "slug.missing-current",
43
+ slugNotUnique: "slug.not-unique",
44
+ stringEmail: "string.email",
45
+ stringExactLength: "string.exact-length",
46
+ stringLowercase: "string.lowercase",
47
+ stringMaximumLength: "string.maximum-length",
48
+ stringMinimumLength: "string.minimum-length",
49
+ stringRegexMatch: "string.regex-match",
50
+ stringRegexMismatch: "string.regex-mismatch",
51
+ stringUppercase: "string.uppercase",
52
+ stringUrlCredentialsNotAllowed: "string.url.credentials-not-allowed",
53
+ stringUrlInvalid: "string.url.invalid",
54
+ stringUrlNotAbsolute: "string.url.not-absolute",
55
+ stringUrlNotRelative: "string.url.not-relative",
56
+ stringUrlSchemeNotAllowed: "string.url.scheme-not-allowed",
57
+ validationException: "validation.exception",
58
+ validationFailed: "validation.failed",
59
+ valueNotAllowed: "value.not-allowed",
60
+ valueRequired: "value.required",
61
+ valueTypeMismatch: "value.type-mismatch"
62
+ }, validationLocaleStrings = {
63
+ "array.exact-length": "Must have exactly {{wantedLength}} items",
64
+ "array.exact-length_blocks": "Must have exactly {{wantedLength}} blocks",
65
+ "array.item-duplicate": "Can't be a duplicate",
66
+ "array.maximum-length": "Must have at most {{maxLength}} items",
67
+ "array.maximum-length_blocks": "Must have at most {{maxLength}} blocks",
68
+ "array.minimum-length": "Must have at least {{minLength}} items",
69
+ "array.minimum-length_blocks": "Must have at least {{minLength}} blocks",
70
+ "date.invalid-format": "Must be a valid ISO-8601 formatted date string",
71
+ "date.maximum": "Must be at or before {{maxDate}}",
72
+ "date.minimum": "Must be at or after {{minDate}}",
73
+ "generic.incorrect-type": "Expected type \"{{expectedType}}\", got \"{{actualType}}\"",
74
+ "generic.not-allowed": "Value did not match any allowed values",
75
+ "generic.not-allowed_hint": "Value \"{{hint}}\" did not match any allowed values",
76
+ "generic.required": "Required",
77
+ "number.greater-than": "Must be greater than {{threshold}}",
78
+ "number.less-than": "Must be less than {{threshold}}",
79
+ "number.maximum": "Must be lower than or equal to {{maxNumber}}",
80
+ "number.maximum-precision": "Max precision is {{limit}}",
81
+ "number.minimum": "Must be greater than or equal to {{minNumber}}",
82
+ "number.non-integer": "Must be an integer",
83
+ "object.asset-required": "Asset is required",
84
+ "object.asset-required_file": "File is required",
85
+ "object.asset-required_image": "Image is required",
86
+ "object.media-not-found": "The asset could not be found in the Media Library",
87
+ "object.not-media-library-asset": "Must be a reference to a Media Library asset",
88
+ "object.not-reference": "Must be a reference to a document",
89
+ "object.reference-not-published": "Referenced document must be published",
90
+ "slug.missing-current": "Slug must have a value",
91
+ "slug.not-object": "Slug must be an object",
92
+ "slug.not-unique": "Slug is already in use",
93
+ "string.email": "Must be a valid email address",
94
+ "string.exact-length": "Must be exactly {{wantedLength}} characters long",
95
+ "string.lowercase": "Must be all lowercase characters",
96
+ "string.maximum-length": "Must be at most {{maxLength}} characters long",
97
+ "string.minimum-length": "Must be at least {{minLength}} characters long",
98
+ "string.regex-does-not-match": "Does not match \"{{name}}\"-pattern",
99
+ "string.regex-match": "Should not match \"{{name}}\"-pattern",
100
+ "string.uppercase": "Must be all uppercase characters",
101
+ "string.url.disallowed-scheme": "Does not match allowed protocols/schemes",
102
+ "string.url.includes-credentials": "Username/password not allowed",
103
+ "string.url.invalid": "Not a valid URL",
104
+ "string.url.not-absolute": "Relative URLs are not allowed",
105
+ "string.url.not-relative": "Only relative URLs are allowed"
106
+ };
107
+ let fallbackLocaleSource;
108
+ function getFallbackLocaleSource() {
109
+ if (fallbackLocaleSource) return fallbackLocaleSource;
110
+ let i18n = createInstance({
111
+ defaultNS: "validation",
112
+ fallbackLng: "en-US",
113
+ initAsync: !1,
114
+ interpolation: { escapeValue: !1 },
115
+ lng: "en-US",
116
+ ns: ["validation"],
117
+ resources: { "en-US": { validation: validationLocaleStrings } },
118
+ supportedLngs: ["en-US"]
119
+ });
120
+ return i18n.init(), fallbackLocaleSource = {
121
+ currentLocale: { id: "en-US" },
122
+ loadNamespaces: (namespaces) => i18n.loadNamespaces(namespaces),
123
+ t: i18n.t
124
+ }, fallbackLocaleSource;
125
+ }
126
+ function resolveConditionalProperty(property, context) {
127
+ let { currentUser, document, parent, value, path } = context;
128
+ return typeof property == "boolean" || property === void 0 ? !!property : property({
129
+ document,
130
+ parent,
131
+ value,
132
+ currentUser,
133
+ path
134
+ }) === !0;
135
+ }
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({
138
+ url: client.getDataUrl("doc", ids.join(",")),
139
+ query: { excludeContent: "true" },
140
+ tag: "documents-availability"
141
+ }).pipe(map((availability) => ({
142
+ availability,
143
+ 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
+ } : {
150
+ id,
151
+ exists: !0
152
+ };
153
+ })), share());
154
+ 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;
159
+ };
160
+ }
161
+ function pathToString(path = []) {
162
+ return path.reduce((target, segment, i) => {
163
+ let segmentType = typeof segment;
164
+ if (segmentType === "number") return `${target}[${segment}]`;
165
+ if (segmentType === "string") return `${target}${i === 0 ? "" : "."}${segment}`;
166
+ if (isKeyedObject(segment)) return `${target}[_key=="${segment._key}"]`;
167
+ throw Error(`Unsupported path segment "${segment}"`);
168
+ }, "");
169
+ }
170
+ function convertToValidationMarker(validatorResult, level, context, fallback = { code: validationMarkerCodes.validationFailed }) {
171
+ if (!context) throw Error("missing context");
172
+ if (validatorResult === !0) return [];
173
+ if (Array.isArray(validatorResult)) return validatorResult.flatMap((child) => convertToValidationMarker(child, level, context, fallback));
174
+ if (typeof validatorResult == "string") return convertToValidationMarker({ message: validatorResult }, level, context, fallback);
175
+ if (typeof validatorResult.message != "string") throw Error(`${pathToString(context.path)}: Validator must return 'true' if valid or an error message as a string on errors`);
176
+ let { message, __internal_metadata } = validatorResult, code = validatorResult.code || fallback.code, details = validatorResult.details || fallback.details, normalizedPaths = [];
177
+ validatorResult.path && normalizedPaths.push(validatorResult.path);
178
+ for (let path of validatorResult.paths || []) normalizedPaths.push(path);
179
+ return normalizedPaths.length ? normalizedPaths.map((path) => ({
180
+ code,
181
+ ...details && { details },
182
+ path: (context.path || []).concat(path),
183
+ level: level || "error",
184
+ item: { message },
185
+ message,
186
+ __internal_metadata
187
+ })) : [{
188
+ code,
189
+ ...details && { details },
190
+ level: level || "error",
191
+ item: { message },
192
+ message,
193
+ path: context.path || [],
194
+ __internal_metadata
195
+ }];
196
+ }
197
+ /**
198
+ * Extracts the correct localized validation message based on given locale source
199
+ *
200
+ * @param message - Localized messages to extract string from
201
+ * @param i18n - Locale source, holding the current locale
202
+ * @returns The localized string, or a fallback "Unknown error" if not found
203
+ * @internal
204
+ */
205
+ function localizeMessage(message, i18n) {
206
+ let { currentLocale } = i18n, locale = currentLocale.id;
207
+ if (message[locale]) return message[locale];
208
+ if (locale.includes("-")) {
209
+ let language = locale.split("-", 1)[0];
210
+ if (message[language]) return message[language];
211
+ }
212
+ return message["en-US"] || message["en-GB"] || message.en || "Unknown validation error (not localized)";
213
+ }
214
+ /**
215
+ * Check if passed message/result is a localized message object
216
+ *
217
+ * @param message - Message to check
218
+ * @returns True if message is a localized message object, false otherwise
219
+ * @internal
220
+ */
221
+ function isLocalizedMessages(message) {
222
+ return message !== !0 && message !== void 0 && typeof message != "string" && isPlainObject(message) && !("message" in message);
223
+ }
224
+ /**
225
+ * Modified version of fast-deep-equal (https://github.com/epoberezkin/fast-deep-equal)
226
+ * MIT-licensed, copyright (c) 2017 Evgeny Poberezkin
227
+ **/
228
+ function deepEqualsIgnoreKey(a, b) {
229
+ if (a === b) return !0;
230
+ if (Array.isArray(a) && Array.isArray(b)) {
231
+ if (a.length != b.length) return !1;
232
+ for (let i = 0; i < a.length; i++) if (!deepEqualsIgnoreKey(a[i], b[i])) return !1;
233
+ return !0;
234
+ }
235
+ if (Array.isArray(a) != Array.isArray(b)) return !1;
236
+ if (a && b && typeof a == "object" && typeof b == "object") {
237
+ let keys = Object.keys(a);
238
+ if (keys.length !== Object.keys(b).length) return !1;
239
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
240
+ if (a instanceof Date != b instanceof Date) return !1;
241
+ if (a instanceof RegExp && b instanceof RegExp) return a.toString() == b.toString();
242
+ if (a instanceof RegExp != b instanceof RegExp) return !1;
243
+ for (let i = 0; i < keys.length; i++) if (keys[i] !== "_key" && !Object.prototype.hasOwnProperty.call(b, keys[i])) return !1;
244
+ for (let i = 0; i < keys.length; i++) {
245
+ let key = keys[i];
246
+ if (key !== "_key" && !deepEqualsIgnoreKey(a[key], b[key])) return !1;
247
+ }
248
+ return !0;
249
+ }
250
+ return !1;
251
+ }
252
+ const _toString = {}.toString, builtIns = [
253
+ Object,
254
+ Function,
255
+ Array,
256
+ String,
257
+ Boolean,
258
+ Number,
259
+ Date,
260
+ RegExp,
261
+ Error
262
+ ];
263
+ function isBuiltIn(_constructor) {
264
+ for (let i = 0; i < builtIns.length; i++) if (builtIns[i] === _constructor) return !0;
265
+ return !1;
266
+ }
267
+ function typeString(obj) {
268
+ let stringType = _toString.call(obj).slice(8, -1);
269
+ if (obj == null) return stringType.toLowerCase();
270
+ let constructorType = obj.constructor;
271
+ return constructorType && !isBuiltIn(constructorType) ? constructorType.name : stringType;
272
+ }
273
+ const SLOW_VALIDATOR_TIMEOUT = 5e3, formatValidationErrors = (options) => {
274
+ let message = options.message || (options.results.length === 1 ? options.results[0]?.message : options.i18n.t("{{messages, list}}", {
275
+ messages: options.results.map((err) => err.message || err.item?.message),
276
+ formatParams: { messages: {
277
+ style: "long",
278
+ type: options.operation
279
+ } }
280
+ }));
281
+ return {
282
+ code: options.operation === "conjunction" ? validationMarkerCodes.ruleAllFailed : validationMarkerCodes.ruleEitherFailed,
283
+ details: { causes: options.results.map(({ code, details, item, message: causeMessage, path }) => ({
284
+ code: code || validationMarkerCodes.validationFailed,
285
+ details,
286
+ message: causeMessage || item?.message,
287
+ path
288
+ })) },
289
+ message: message || "Validation failed"
290
+ };
291
+ }, genericValidators = {
292
+ type: (expectedType, value, message, { i18n }) => {
293
+ let actualType = typeString(value);
294
+ return actualType !== expectedType && actualType !== "undefined" ? {
295
+ code: validationMarkerCodes.valueTypeMismatch,
296
+ details: {
297
+ actualType,
298
+ expectedType
299
+ },
300
+ message: message || i18n.t("validation:generic.incorrect-type", {
301
+ actualType,
302
+ expectedType
303
+ })
304
+ } : !0;
305
+ },
306
+ presence: (expected, value, message, { i18n }) => value === void 0 && expected === "required" ? {
307
+ code: validationMarkerCodes.valueRequired,
308
+ message: message || i18n.t("validation:generic.required")
309
+ } : !0,
310
+ all: async (children, value, message, context) => {
311
+ let results = (await Promise.all(children.map((child) => child.validate(value, context)))).flat();
312
+ return results.length === 0 || formatValidationErrors({
313
+ message,
314
+ results,
315
+ operation: "conjunction",
316
+ i18n: context.i18n
317
+ });
318
+ },
319
+ either: async (children, value, message, context) => {
320
+ let resolved = await Promise.all(children.map((child) => child.validate(value, context))), results = resolved.flat();
321
+ return resolved.find((result) => !result.length) ? !0 : formatValidationErrors({
322
+ message,
323
+ results,
324
+ operation: "disjunction",
325
+ i18n: context.i18n
326
+ });
327
+ },
328
+ valid: (allowedValues, actual, message, { i18n }) => {
329
+ let valueType = typeof actual;
330
+ if (valueType === "undefined") return !0;
331
+ let value = (valueType === "number" || valueType === "string") && `${actual}`, strValue = value && value.length > 30 ? `${value.slice(0, 30)}…` : value;
332
+ return allowedValues.some((expected) => deepEqualsIgnoreKey(expected, actual)) ? !0 : {
333
+ code: validationMarkerCodes.valueNotAllowed,
334
+ details: { allowedValuesCount: allowedValues.length },
335
+ message: message || i18n.t("validation:generic.not-allowed", value ? {
336
+ context: "hint",
337
+ replace: { hint: strValue }
338
+ } : {})
339
+ };
340
+ },
341
+ custom: async (fn, value, message, context) => {
342
+ let slowTimer = setTimeout(() => {
343
+ context.environment === "studio" && console.warn(`Custom validator at ${pathToString(context.path)} has taken more than ${SLOW_VALIDATOR_TIMEOUT}ms to respond`);
344
+ }, SLOW_VALIDATOR_TIMEOUT), result;
345
+ try {
346
+ result = await fn(value, context);
347
+ } finally {
348
+ clearTimeout(slowTimer);
349
+ }
350
+ return isLocalizedMessages(result) ? localizeMessage(result, context.i18n) : typeof result == "string" && message || result;
351
+ }
352
+ }, arrayValidators = {
353
+ ...genericValidators,
354
+ min: (minLength, value, message, { i18n, type }) => {
355
+ if (!value || value.length >= minLength) return !0;
356
+ let context = isArrayOfBlocksSchemaType(type) ? "blocks" : void 0;
357
+ return {
358
+ code: validationMarkerCodes.arrayMinimumLength,
359
+ details: {
360
+ actualLength: value.length,
361
+ minimumLength: minLength
362
+ },
363
+ message: message || i18n.t("validation:array.minimum-length", {
364
+ minLength,
365
+ context
366
+ })
367
+ };
368
+ },
369
+ max: (maxLength, value, message, { i18n, type }) => {
370
+ if (!value || value.length <= maxLength) return !0;
371
+ let context = isArrayOfBlocksSchemaType(type) ? "blocks" : void 0;
372
+ return {
373
+ code: validationMarkerCodes.arrayMaximumLength,
374
+ details: {
375
+ actualLength: value.length,
376
+ maximumLength: maxLength
377
+ },
378
+ message: message || i18n.t("validation:array.maximum-length", {
379
+ maxLength,
380
+ context
381
+ })
382
+ };
383
+ },
384
+ length: (wantedLength, value, message, { i18n, type }) => {
385
+ if (!value || value.length === wantedLength) return !0;
386
+ let context = isArrayOfBlocksSchemaType(type) ? "blocks" : void 0;
387
+ return {
388
+ code: validationMarkerCodes.arrayExactLength,
389
+ details: {
390
+ actualLength: value.length,
391
+ expectedLength: wantedLength
392
+ },
393
+ message: message || i18n.t("validation:array.exact-length", {
394
+ wantedLength,
395
+ context
396
+ })
397
+ };
398
+ },
399
+ presence: (flag, value, message, { i18n }) => flag === "required" && !value ? {
400
+ code: validationMarkerCodes.valueRequired,
401
+ message: message || i18n.t("validation:generic.required", { context: "array" })
402
+ } : !0,
403
+ valid: (allowedValues, values, message, { i18n }) => {
404
+ if (values === void 0) return !0;
405
+ let paths = [];
406
+ for (let i = 0; i < values.length; i++) {
407
+ let value = values[i];
408
+ if (allowedValues.some((expected) => deepEqualsIgnoreKey(expected, value))) continue;
409
+ let pathSegment = value && value._key ? { _key: value._key } : i;
410
+ paths.push([pathSegment]);
411
+ }
412
+ let sharedMessage = message || i18n.t("validation:generic.not-allowed");
413
+ return paths.map((path) => ({
414
+ code: validationMarkerCodes.valueNotAllowed,
415
+ details: { allowedValuesCount: allowedValues.length },
416
+ message: sharedMessage,
417
+ path
418
+ }));
419
+ },
420
+ unique: (_unused, value, message, { i18n }) => {
421
+ let dupeIndices = [];
422
+ if (!value) return !0;
423
+ for (let x = 0; x < value.length; x++) for (let y = x + 1; y < value.length; y++) {
424
+ let itemA = value[x], itemB = value[y];
425
+ deepEqualsIgnoreKey(itemA, itemB) && (dupeIndices.indexOf(x) === -1 && dupeIndices.push(x), dupeIndices.indexOf(y) === -1 && dupeIndices.push(y));
426
+ }
427
+ let paths = dupeIndices.map((idx) => {
428
+ let item = value[idx];
429
+ return [item && item._key ? { _key: item._key } : idx];
430
+ }), sharedMessage = message || i18n.t("validation:array.item-duplicate");
431
+ return paths.map((path) => ({
432
+ code: validationMarkerCodes.arrayDuplicateItem,
433
+ message: sharedMessage,
434
+ path
435
+ }));
436
+ }
437
+ }, booleanValidators = {
438
+ ...genericValidators,
439
+ presence: (flag, value, message, { i18n }) => flag === "required" && typeof value != "boolean" ? {
440
+ code: validationMarkerCodes.valueRequired,
441
+ message: message || i18n.t("validation:generic.required", { context: "boolean" })
442
+ } : !0
443
+ };
444
+ function isRecord$1(obj) {
445
+ return typeof obj == "object" && !!obj && !Array.isArray(obj);
446
+ }
447
+ const isoDate = /^(?:[-+]\d{2})?(?:\d{4}(?!\d{2}\b))(?:(-?)(?:(?:0[1-9]|1[0-2])(?:\1(?:[12]\d|0[1-9]|3[01]))?|W(?:[0-4]\d|5[0-2])(?:-?[1-7])?|(?:00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[1-6])))(?![T]$|[T][\d]+Z$)(?:[T\s](?:(?:(?:[01]\d|2[0-3])(?:(:?)[0-5]\d)?|24:?00)(?:[.,]\d+(?!:))?)(?:\2[0-5]\d(?:[.,]\d+)?)?(?:[Z]|(?:[+-])(?:[01]\d|2[0-3])(?::?[0-5]\d)?)?)?)?$/, getFormattedDate = (type = "", value, options) => {
448
+ let dateFormat = options?.dateFormat || legacyDateFormat.DEFAULT_DATE_FORMAT, timeFormat = options?.timeFormat || legacyDateFormat.DEFAULT_TIME_FORMAT;
449
+ return legacyDateFormat.format(value, type === "date" ? dateFormat : `${dateFormat} ${timeFormat}`, { useUTC: type === "date" });
450
+ };
451
+ function parseDate(date, throwOnError = !1) {
452
+ if (!date) return null;
453
+ if (date === "now") return /* @__PURE__ */ new Date();
454
+ let parsed = new Date(date), isInvalid = isNaN(parsed.getTime());
455
+ if (isInvalid && throwOnError) throw Error(`Unable to parse "${date}" to a date`);
456
+ return isInvalid ? null : parsed;
457
+ }
458
+ const dateValidators = {
459
+ ...genericValidators,
460
+ type: (_unused, value, message, { i18n }) => value === void 0 || isoDate.test(`${value}`) ? !0 : {
461
+ code: validationMarkerCodes.dateInvalidFormat,
462
+ details: { actualValue: value },
463
+ message: message || i18n.t("validation:date.invalid-format")
464
+ },
465
+ min: (minDate, value, message, { type, i18n }) => {
466
+ let dateVal = parseDate(value), minDateVal = parseDate(minDate, !0);
467
+ if (!dateVal || !value || dateVal >= minDateVal) return !0;
468
+ if (!type) throw Error("`type` was not provided in validation context.");
469
+ let dateTimeOptions = isRecord$1(type.options) ? type.options : {};
470
+ return {
471
+ code: validationMarkerCodes.dateMinimum,
472
+ details: {
473
+ actualValue: value,
474
+ minimum: minDate
475
+ },
476
+ message: message || i18n.t("validation:date.minimum", {
477
+ minDate: getFormattedDate(type.name, minDateVal, dateTimeOptions),
478
+ providedMinDate: minDate
479
+ })
480
+ };
481
+ },
482
+ max: (maxDate, value, message, { type, i18n }) => {
483
+ let dateVal = parseDate(value), maxDateVal = parseDate(maxDate, !0);
484
+ if (!dateVal || !value || dateVal <= maxDateVal) return !0;
485
+ if (!type) throw Error("`type` was not provided in validation context.");
486
+ let dateTimeOptions = isRecord$1(type.options) ? type.options : {};
487
+ return {
488
+ code: validationMarkerCodes.dateMaximum,
489
+ details: {
490
+ actualValue: value,
491
+ maximum: maxDate
492
+ },
493
+ message: message || i18n.t("validation:date.maximum", {
494
+ maxDate: getFormattedDate(type.name, maxDateVal, dateTimeOptions),
495
+ providedMaxDate: maxDate
496
+ })
497
+ };
498
+ }
499
+ }, precisionRx = /(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/, numberValidators = {
500
+ ...genericValidators,
501
+ integer: (_unused, value, message, { i18n }) => Number.isInteger(value) ? !0 : {
502
+ code: validationMarkerCodes.numberInteger,
503
+ details: { actualValue: value },
504
+ message: message || i18n.t("validation:number.non-integer")
505
+ },
506
+ precision: (limit, value, message, { i18n }) => {
507
+ if (value === void 0) return !0;
508
+ let places = value.toString().match(precisionRx), decimals = Math.max((places[1] ? places[1].length : 0) - (places[2] ? parseInt(places[2], 10) : 0), 0);
509
+ return decimals > limit ? {
510
+ code: validationMarkerCodes.numberPrecision,
511
+ details: {
512
+ actualPrecision: decimals,
513
+ maximumPrecision: limit
514
+ },
515
+ message: message || i18n.t("validation:number.maximum-precision", { limit })
516
+ } : !0;
517
+ },
518
+ min: (minNumber, value, message, { i18n }) => value >= minNumber || {
519
+ code: validationMarkerCodes.numberMinimum,
520
+ details: {
521
+ actualValue: value,
522
+ minimum: minNumber
523
+ },
524
+ message: message || i18n.t("validation:number.minimum", { minNumber })
525
+ },
526
+ max: (maxNumber, value, message, { i18n }) => value <= maxNumber || {
527
+ code: validationMarkerCodes.numberMaximum,
528
+ details: {
529
+ actualValue: value,
530
+ maximum: maxNumber
531
+ },
532
+ message: message || i18n.t("validation:number.maximum", { maxNumber })
533
+ },
534
+ greaterThan: (threshold, value, message, { i18n }) => value > threshold || {
535
+ code: validationMarkerCodes.numberGreaterThan,
536
+ details: {
537
+ actualValue: value,
538
+ threshold
539
+ },
540
+ message: message || i18n.t("validation:number.greater-than", { threshold })
541
+ },
542
+ lessThan: (threshold, value, message, { i18n }) => value < threshold || {
543
+ code: validationMarkerCodes.numberLessThan,
544
+ details: {
545
+ actualValue: value,
546
+ threshold
547
+ },
548
+ message: message || i18n.t("validation:number.less-than", { threshold })
549
+ }
550
+ }, metaKeys = [
551
+ "_key",
552
+ "_type",
553
+ "_weak"
554
+ ], objectValidators = {
555
+ ...genericValidators,
556
+ presence: (expected, value, message, { i18n }) => {
557
+ if (expected !== "required") return !0;
558
+ let keys = value && Object.keys(value).filter((key) => !metaKeys.includes(key));
559
+ return value === void 0 || keys && keys.length === 0 ? {
560
+ code: validationMarkerCodes.valueRequired,
561
+ message: message || i18n.t("validation:generic.required", { context: "object" })
562
+ } : !0;
563
+ },
564
+ reference: async (_unused, value, message, context) => {
565
+ if (!value) return !0;
566
+ let { type, document, getDocumentExists, i18n } = context;
567
+ if (!isReference(value)) return {
568
+ code: validationMarkerCodes.referenceInvalid,
569
+ details: { actualType: typeString(value) },
570
+ message: message || i18n.t("validation:object.not-reference")
571
+ };
572
+ if (!type) throw Error("`type` was not provided in validation context");
573
+ if ("weak" in type && type.weak) return !0;
574
+ if (!getDocumentExists) throw Error("`getDocumentExists` was not provided in validation context");
575
+ let documentId = document?._id;
576
+ return documentId && value._ref == getPublishedId(documentId) || await getDocumentExists({ id: value._ref }) ? !0 : {
577
+ code: validationMarkerCodes.referenceNotPublished,
578
+ details: { referenceId: value._ref },
579
+ message: i18n.t("validation:object.reference-not-published", { documentId: value._ref })
580
+ };
581
+ },
582
+ assetRequired: (flag, value, message, { i18n }) => !value || !value.asset || !value.asset._ref ? {
583
+ __internal_metadata: { name: "assetRequired" },
584
+ code: validationMarkerCodes.assetRequired,
585
+ details: { assetType: flag.assetType },
586
+ message: message || i18n.t("validation:object.asset-required", { context: flag.assetType || "" })
587
+ } : !0,
588
+ media: async (fn, value, message, context) => {
589
+ let slowTimer = setTimeout(() => {
590
+ context.environment === "studio" && console.warn(`Media validator at ${pathToString(context.path)} has taken more than ${SLOW_VALIDATOR_TIMEOUT}ms to respond`);
591
+ }, SLOW_VALIDATOR_TIMEOUT);
592
+ if (!value) return !0;
593
+ if (!value || !value.media || !value.media._ref) return {
594
+ code: validationMarkerCodes.mediaInvalidReference,
595
+ message: context.i18n.t("validation:object.not-media-library-asset")
596
+ };
597
+ let result = !0;
598
+ try {
599
+ let [type, libraryId, documentId] = value.media._ref.split(":", 3), resourceConfig = { resource: {
600
+ type,
601
+ id: libraryId
602
+ } }, asset = await context.getClient({ apiVersion: "2025-02-19" }).withConfig(resourceConfig).fetch("*[_id == $id] { ..., 'currentVersion': @.currentVersion-> { ... } }[0]", { id: documentId });
603
+ if (!asset) return console.warn(`${context.i18n.t("validation:object.media-not-found")}\nAsset ID: ${value.media._ref}`), {
604
+ code: validationMarkerCodes.mediaNotFound,
605
+ details: { referenceId: value.media._ref },
606
+ message: context.i18n.t("validation:object.media-not-found")
607
+ };
608
+ result = await fn({
609
+ media: { asset },
610
+ value
611
+ }, context);
612
+ } catch (err) {
613
+ throw Error(`Media validator at ${pathToString(context.path)} failed with an error: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
614
+ } finally {
615
+ clearTimeout(slowTimer);
616
+ }
617
+ let validationErrorMetadata = { __internal_metadata: { name: "media" } };
618
+ return result === !0 || Array.isArray(result) ? result : [result].map((res) => typeof res == "string" ? {
619
+ ...validationErrorMetadata,
620
+ message: message || res
621
+ } : isLocalizedMessages(res) ? {
622
+ ...validationErrorMetadata,
623
+ message: message || localizeMessage(res, context.i18n)
624
+ } : {
625
+ ...validationErrorMetadata,
626
+ ...res,
627
+ message: message || res.message
628
+ });
629
+ }
630
+ }, DUMMY_ORIGIN = "http://sanity", isRelativeUrl = (url) => /^\.*\//.test(url), emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, typeValidators = {
631
+ Boolean: booleanValidators,
632
+ Number: numberValidators,
633
+ String: {
634
+ ...genericValidators,
635
+ min: (minLength, value, message, { i18n }) => !value || value.length >= minLength || {
636
+ code: validationMarkerCodes.stringMinimumLength,
637
+ details: {
638
+ actualLength: value.length,
639
+ minimumLength: minLength
640
+ },
641
+ message: message || i18n.t("validation:string.minimum-length", { minLength })
642
+ },
643
+ max: (maxLength, value, message, { i18n }) => !value || value.length <= maxLength || {
644
+ code: validationMarkerCodes.stringMaximumLength,
645
+ details: {
646
+ actualLength: value.length,
647
+ maximumLength: maxLength
648
+ },
649
+ message: message || i18n.t("validation:string.maximum-length", { maxLength })
650
+ },
651
+ length: (wantedLength, value, message, { i18n }) => {
652
+ let strValue = value || "";
653
+ return strValue.length === wantedLength || {
654
+ code: validationMarkerCodes.stringExactLength,
655
+ details: {
656
+ actualLength: strValue.length,
657
+ expectedLength: wantedLength
658
+ },
659
+ message: message || i18n.t("validation:string.exact-length", { wantedLength })
660
+ };
661
+ },
662
+ uri: (constraints, value, message, { i18n }) => {
663
+ let strValue = value || "";
664
+ if (!strValue) return !0;
665
+ let { options } = constraints, { allowCredentials, relativeOnly } = options, allowRelative = options.allowRelative || relativeOnly, url;
666
+ try {
667
+ url = allowRelative ? new URL(strValue, DUMMY_ORIGIN) : new URL(strValue);
668
+ } catch {
669
+ return {
670
+ code: validationMarkerCodes.stringUrlInvalid,
671
+ message: message || i18n.t("validation:string.url.invalid")
672
+ };
673
+ }
674
+ if (relativeOnly && url.origin !== DUMMY_ORIGIN) return {
675
+ code: validationMarkerCodes.stringUrlNotRelative,
676
+ message: message || i18n.t("validation:string.url.not-relative")
677
+ };
678
+ if (!allowRelative && url.origin === DUMMY_ORIGIN && isRelativeUrl(strValue)) return {
679
+ code: validationMarkerCodes.stringUrlNotAbsolute,
680
+ message: message || i18n.t("validation:string.url.not-absolute")
681
+ };
682
+ if (!allowCredentials && (url.username || url.password)) return {
683
+ code: validationMarkerCodes.stringUrlCredentialsNotAllowed,
684
+ message: message || i18n.t("validation:string.url.includes-credentials")
685
+ };
686
+ let urlScheme = url.protocol.replace(/:$/, "");
687
+ return isRelativeUrl(strValue) || options.scheme.some((scheme) => scheme.test(urlScheme)) ? !0 : {
688
+ code: validationMarkerCodes.stringUrlSchemeNotAllowed,
689
+ details: { scheme: urlScheme },
690
+ message: message || i18n.t("validation:string.url.disallowed-scheme", { scheme: urlScheme })
691
+ };
692
+ },
693
+ stringCasing: (casing, value, message, { i18n }) => {
694
+ let strValue = value || "";
695
+ return casing === "uppercase" && strValue !== strValue.toLocaleUpperCase() ? {
696
+ code: validationMarkerCodes.stringUppercase,
697
+ message: message || i18n.t("validation:string.uppercase")
698
+ } : casing === "lowercase" && strValue !== strValue.toLocaleLowerCase() ? {
699
+ code: validationMarkerCodes.stringLowercase,
700
+ message: message || i18n.t("validation:string.lowercase")
701
+ } : !0;
702
+ },
703
+ presence: (flag, value, message, { i18n }) => flag === "required" && !value ? {
704
+ code: validationMarkerCodes.valueRequired,
705
+ message: message || i18n.t("validation:generic.required", { context: "string" })
706
+ } : !0,
707
+ regex: (options, value, message, { i18n }) => {
708
+ let { pattern, name, invert } = options, regName = name || `${pattern.toString()}`, strValue = value || "";
709
+ pattern.lastIndex = 0;
710
+ let matches = pattern.test(strValue);
711
+ return !invert && !matches || invert && matches ? {
712
+ code: invert ? validationMarkerCodes.stringRegexMatch : validationMarkerCodes.stringRegexMismatch,
713
+ details: {
714
+ invert,
715
+ name: regName,
716
+ pattern: pattern.source
717
+ },
718
+ message: message || (invert ? i18n.t("validation:string.regex-match", { name: regName }) : i18n.t("validation:string.regex-does-not-match", { name: regName }))
719
+ } : !0;
720
+ },
721
+ email: (_unused, value, message, { i18n }) => {
722
+ let strValue = `${value || ""}`.trim();
723
+ return !strValue || emailRegex.test(strValue) ? !0 : {
724
+ code: validationMarkerCodes.stringEmail,
725
+ message: message || i18n.t("validation:string.email")
726
+ };
727
+ }
728
+ },
729
+ Array: arrayValidators,
730
+ Object: objectValidators,
731
+ Date: dateValidators
732
+ }, isFieldRef = (constraint) => typeof constraint != "object" || !constraint ? !1 : constraint.type === Rule$2.FIELD_REF, EMPTY_ARRAY = [], fallbackCodeForRule = (flag) => flag === "custom" ? validationMarkerCodes.custom : flag === "media" ? validationMarkerCodes.mediaCustom : flag === "all" ? validationMarkerCodes.ruleAllFailed : flag === "either" ? validationMarkerCodes.ruleEitherFailed : validationMarkerCodes.validationFailed, Rule$2 = class Rule extends Rule$1 {
733
+ static array = (def) => new Rule(def).type("Array");
734
+ static object = (def) => new Rule(def).type("Object");
735
+ static string = (def) => new Rule(def).type("String");
736
+ static number = (def) => new Rule(def).type("Number");
737
+ static boolean = (def) => new Rule(def).type("Boolean");
738
+ static dateTime = (def) => new Rule(def).type("Date");
739
+ clone() {
740
+ let rule = new Rule();
741
+ 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
+ }
743
+ async validate(value, { __internal = {}, ...context }) {
744
+ let { customValidationConcurrencyLimiter } = __internal, valueIsEmpty = value == null;
745
+ if (valueIsEmpty && this._required === "optional") return EMPTY_ARRAY;
746
+ let rules = this._required === void 0 && valueIsEmpty ? this._rules.filter((curr) => curr.flag === "custom") : this._rules, validators = this._type && typeValidators[this._type] || genericValidators;
747
+ return (await Promise.all(rules.map(async (curr) => {
748
+ if (curr.flag === void 0) throw Error("Invalid rule, did not contain \"flag\"-property");
749
+ let validator = validators[curr.flag];
750
+ if (!validator) {
751
+ let forType = this._type ? `type "${this._type}"` : "rule without declared type";
752
+ throw Error(`Validator for flag "${curr.flag}" not found for ${forType}`);
753
+ }
754
+ let specConstraint = "constraint" in curr ? curr.constraint : null;
755
+ if (isFieldRef(specConstraint) && (specConstraint = get(context.parent, specConstraint.path)), curr.flag === "custom" && customValidationConcurrencyLimiter && !specConstraint?.bypassConcurrencyLimit) {
756
+ 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
+ };
765
+ }
766
+ let message = isLocalizedMessages(this._message) ? localizeMessage(this._message, context.i18n) : this._message;
767
+ try {
768
+ return convertToValidationMarker(await validator(specConstraint, value, message, context), this._level, context, { code: fallbackCodeForRule(curr.flag) });
769
+ } catch (err) {
770
+ let errorMessage = `${pathToString(context.path)}: Exception occurred while validating value: ${err.message}`;
771
+ return convertToValidationMarker({
772
+ code: validationMarkerCodes.validationException,
773
+ message: errorMessage
774
+ }, "error", context);
775
+ }
776
+ }))).flat();
777
+ }
778
+ }, memoizedWarnOnArraySlug = memoize(warnOnArraySlug);
779
+ function serializePath(path) {
780
+ return path.reduce((target, part, i) => {
781
+ let isIndex = typeof part == "number", isKey = isKeyedObject(part);
782
+ return `${target}${isIndex || isKey ? "[]" : `${i === 0 ? "" : "."}${part}`}`;
783
+ }, "");
784
+ }
785
+ const defaultIsUnique = (slug, context) => {
786
+ let { getClient, document, path, type } = context, schemaOptions = type?.options;
787
+ if (!document) throw Error("`document` was not provided in validation context.");
788
+ if (!path) throw Error("`path` was not provided in validation context.");
789
+ let disableArrayWarning = schemaOptions?.disableArrayWarning || !1, docType = document._type, atPath = serializePath(path.concat("current"));
790
+ !disableArrayWarning && atPath.includes("[]") && context.environment === "studio" && memoizedWarnOnArraySlug(serializePath(path));
791
+ let constraints = [
792
+ "_type == $docType",
793
+ "!sanity::versionOf($published)",
794
+ `${atPath} == $slug`
795
+ ].join(" && ");
796
+ return getClient({ apiVersion: "2025-02-19" }).withConfig({ perspective: "raw" }).fetch(`!defined(*[${constraints}][0]._id)`, {
797
+ docType,
798
+ published: getPublishedId(document._id),
799
+ slug
800
+ }, { tag: "validation.slug-is-unique" });
801
+ };
802
+ function warnOnArraySlug(serializedPath) {
803
+ console.warn([
804
+ `Slug field at path ${serializedPath} is within an array and cannot be automatically checked for uniqueness`,
805
+ "If you need to check for uniqueness, provide your own \"isUnique\" method",
806
+ "To disable this message, set `disableArrayWarning: true` on the slug `options` field"
807
+ ].join("\n"));
808
+ }
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) => {
816
+ if (!value) return !0;
817
+ let { i18n } = context;
818
+ if (typeof value != "object" || Array.isArray(value)) return {
819
+ code: validationMarkerCodes.slugInvalidType,
820
+ details: { actualType: typeString(value) },
821
+ message: i18n.t("validation:slug.not-object")
822
+ };
823
+ if (!isSlug(value) || value.current.trim().length === 0) return {
824
+ code: validationMarkerCodes.slugMissingCurrent,
825
+ message: i18n.t("validation:slug.missing-current")
826
+ };
827
+ let isUnique = context?.type?.options?.isUnique || defaultIsUnique, slugContext = {
828
+ ...context,
829
+ parent: context.parent,
830
+ type: context.type,
831
+ defaultIsUnique
832
+ };
833
+ return await isUnique(value.current, slugContext) ? !0 : {
834
+ code: validationMarkerCodes.slugNotUnique,
835
+ details: { slug: value.current },
836
+ message: i18n.t("validation:slug.not-unique", { slug: value.current })
837
+ };
838
+ }, ruleConstraintTypes = {
839
+ array: !0,
840
+ boolean: !0,
841
+ date: !0,
842
+ number: !0,
843
+ object: !0,
844
+ string: !0
845
+ }, isRuleConstraint = (typeString) => typeString in ruleConstraintTypes;
846
+ function getTypeChain(type, visited = /* @__PURE__ */ new Set()) {
847
+ return !type || visited.has(type) ? [] : (visited.add(type), [...type.type ? getTypeChain(type.type, visited) : [], type]);
848
+ }
849
+ function baseRuleReducer(inputRule, type) {
850
+ let baseRule = inputRule;
851
+ isRuleConstraint(type.jsonType) && (baseRule = baseRule.type(type.jsonType));
852
+ 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;
854
+ }
855
+ function hasValueField(typeDef) {
856
+ 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;
857
+ }
858
+ function extractValueFromListOption(option, typeDef) {
859
+ return typeDef.jsonType === "object" && hasValueField(typeDef) || option.value === void 0 ? option : option.value;
860
+ }
861
+ const isUriSpec = (spec) => spec.flag === "uri";
862
+ function omitLeakedDefaultUri(rules, typeDef) {
863
+ 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);
865
+ return rules.some((rule) => rule._rules.some(isCustomUri)) ? rules.map((rule) => {
866
+ if (!rule._rules.some(isDefaultUri)) return rule;
867
+ let cleaned = rule.clone();
868
+ return cleaned._rules = rule._rules.filter((spec) => !isDefaultUri(spec)), cleaned;
869
+ }) : rules;
870
+ }
871
+ function normalizeValidationRules(typeDef, context) {
872
+ if (!typeDef) return [];
873
+ let validation = typeDef.validation;
874
+ if (Array.isArray(validation)) return omitLeakedDefaultUri(validation.flatMap((i) => normalizeValidationRules({
875
+ ...typeDef,
876
+ validation: i
877
+ }, context)), typeDef);
878
+ let baseRule = Object.values(getTypeChain(typeDef).reduce((acc, type) => (acc[type.name] = type, acc), {})).reduce(baseRuleReducer, new Rule$2(typeDef));
879
+ return validation && typeof validation == "object" ? [validation] : validation && typeof validation == "function" ? normalizeValidationRules({
880
+ ...typeDef,
881
+ validation: validation(baseRule, context)
882
+ }, context) : [baseRule];
883
+ }
884
+ /**
885
+ * Simple requestIdleCallback polyfill
886
+ * Can be removed when all browsers support requestIdleCallback: https://caniuse.com/requestidlecallback
887
+ * @param callback -
888
+ * @param options -
889
+ */
890
+ const requestIdleCallbackShim = function requestIdleCallbackShim(callback, _options) {
891
+ let start = Date.now();
892
+ return globalThis.setTimeout(() => {
893
+ callback({
894
+ didTimeout: !1,
895
+ timeRemaining() {
896
+ return Math.max(0, Date.now() - start);
897
+ }
898
+ });
899
+ }, 0);
900
+ }, cancelIdleCallbackShim = function cancelIdleCallbackShim(handle) {
901
+ return globalThis.clearTimeout(handle);
902
+ }, win = typeof window > "u" ? void 0 : window, requestIdleCallback = win?.requestIdleCallback || requestIdleCallbackShim, cancelIdleCallback = win?.cancelIdleCallback || cancelIdleCallbackShim, unknownFieldsValidator = (type) => (value) => {
903
+ if (typeof value != "object" || !value) return !0;
904
+ let fieldNames = new Set(type.fields?.map((field) => field.name));
905
+ return Object.keys(value).filter((key) => !key.startsWith("_")).filter((key) => !fieldNames.has(key)).map((unknownField) => ({
906
+ code: validationMarkerCodes.objectUnknownField,
907
+ details: {
908
+ fieldName: unknownField,
909
+ typeName: type.name
910
+ },
911
+ message: `Field '${unknownField}' does not exist on type '${type.name}'`,
912
+ path: [unknownField]
913
+ }));
914
+ }, DEFAULT_VALIDATION_CLIENT_OPTIONS = { apiVersion: "2025-02-19" }, isRecord = (maybeRecord) => typeof maybeRecord == "object" && !!maybeRecord && !Array.isArray(maybeRecord);
915
+ /**
916
+ * Recursively extracts all `_fieldRules` from a rule and its nested constraints.
917
+ * This handles cases where `Rule.fields()` is used inside `Rule.all()` or `Rule.either()`.
918
+ */
919
+ function extractFieldRulesFromRule(rule) {
920
+ let results = [];
921
+ rule._fieldRules && results.push(rule._fieldRules);
922
+ for (let ruleSpec of rule._rules) if (ruleSpec.flag === "all" || ruleSpec.flag === "either") {
923
+ let childRules = ruleSpec.constraint;
924
+ if (Array.isArray(childRules)) for (let childRule of childRules) results.push(...extractFieldRulesFromRule(childRule));
925
+ }
926
+ return results;
927
+ }
928
+ /**
929
+ * @internal
930
+ */
931
+ function resolveTypeForArrayItem(item, candidates) {
932
+ if (candidates.length === 1) return candidates[0];
933
+ let itemType = isTypedObject(item) && item._type, primitive = item == null || !itemType && typeString(item).toLowerCase();
934
+ return primitive && primitive !== "object" ? candidates.find((candidate) => candidate.jsonType === primitive) : candidates.find((candidate) => candidate.type?.name === itemType) || candidates.find((candidate) => candidate.name === itemType) || candidates.find((candidate) => candidate.name === "object" && primitive === "object");
935
+ }
936
+ /**
937
+ * Validates a document against the schema in a resolved Studio workspace or source.
938
+ *
939
+ * @beta
940
+ * @deprecated Prefer {@link validateDocument} with `{document, schema, client}` for new code.
941
+ */
942
+ function validateDocumentWithWorkspace({ document, workspace, getClient = workspace.getClient, getDocumentExists, environment = "studio", maxCustomValidationConcurrency, maxFetchConcurrency, currentUser }) {
943
+ return validateDocumentInternal({
944
+ currentUser,
945
+ document,
946
+ environment,
947
+ getClient,
948
+ getDocumentExists,
949
+ i18n: workspace.i18n,
950
+ maxCustomValidationConcurrency,
951
+ maxFetchConcurrency,
952
+ schema: workspace.schema
953
+ });
954
+ }
955
+ function validateDocument(options) {
956
+ if ("workspace" in options) return validateDocumentWithWorkspace(options);
957
+ let { client, document, schema, ...internalOptions } = options;
958
+ return validateDocumentInternal({
959
+ ...internalOptions,
960
+ document,
961
+ environment: "cli",
962
+ getClient: ({ apiVersion }) => client.withConfig({ apiVersion }),
963
+ i18n: getFallbackLocaleSource(),
964
+ schema
965
+ });
966
+ }
967
+ /** @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({
971
+ document,
972
+ getClient: getConcurrencyLimitedClient,
973
+ i18n,
974
+ schema,
975
+ getDocumentExists: getDocumentExists || createBatchedGetDocumentExists(getClient(DEFAULT_VALIDATION_CLIENT_OPTIONS)),
976
+ environment,
977
+ maxCustomValidationConcurrency,
978
+ currentUser
979
+ }));
980
+ }
981
+ const customValidationConcurrencyLimiters = /* @__PURE__ */ new WeakMap();
982
+ /**
983
+ * Validates a document against the given schema, returning an Observable
984
+ * @internal
985
+ */
986
+ function validateDocumentObservable({ document, getClient, i18n = getFallbackLocaleSource(), schema, getDocumentExists, environment, maxCustomValidationConcurrency, currentUser }) {
987
+ if (typeof document?._type != "string") throw Error("Tried to validate a value without a '_type'");
988
+ 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([{
990
+ code: validationMarkerCodes.documentUnknownType,
991
+ details: { documentType: document._type },
992
+ level: "warning",
993
+ message: `Could not find schema type for type '${document._type}', skipping validation`,
994
+ path: []
995
+ }]);
996
+ 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: []
1020
+ };
1021
+ return of([errorMarker]);
1022
+ }));
1023
+ }
1024
+ function validateItem(opts) {
1025
+ return lastValueFrom(validateItemObservable(opts));
1026
+ }
1027
+ function validateItemObservable({ value, type, path = [], parent, customValidationConcurrencyLimiter, environment, ...restOfContext }) {
1028
+ let ancestorHidden = restOfContext.hidden === !0, resolveHiddenForType = (schemaType, schemaValue, schemaParent, schemaPath, ancestorHiddenValue) => schemaType ? ancestorHiddenValue || resolveConditionalProperty(schemaType.hidden, {
1029
+ ...restOfContext,
1030
+ parent: schemaParent,
1031
+ value: schemaValue,
1032
+ path: schemaPath || [],
1033
+ currentUser: restOfContext.currentUser ?? null
1034
+ }) : ancestorHiddenValue, hidden = resolveHiddenForType(type, value, parent, path, ancestorHidden), addUnknownFieldsValidator = (rule) => type?.jsonType === "object" && getTypeChain(type).find((t) => [
1035
+ "object",
1036
+ "document",
1037
+ "file",
1038
+ "image"
1039
+ ].includes(t.name)) && environment !== "studio" ? rule.custom(unknownFieldsValidator(type), { bypassConcurrencyLimit: !0 }).warning() : rule, rules = normalizeValidationRules(type, {
1040
+ ...restOfContext,
1041
+ hidden,
1042
+ environment,
1043
+ parent,
1044
+ path,
1045
+ type
1046
+ }), selfChecks = rules.map(addUnknownFieldsValidator).map((rule) => defer(() => rule.validate(value, {
1047
+ ...restOfContext,
1048
+ environment,
1049
+ hidden,
1050
+ parent,
1051
+ path,
1052
+ type,
1053
+ __internal: { customValidationConcurrencyLimiter }
1054
+ }))), nestedChecks = [], selfIsRequired = rules.some((rule) => rule.isRequired());
1055
+ if (type?.jsonType === "object" && (value || value == null && selfIsRequired)) {
1056
+ let fieldTypes = type.fields.reduce((acc, field) => (acc[field.name] = field.type, acc), {});
1057
+ nestedChecks = nestedChecks.concat(rules.flatMap((rule) => extractFieldRulesFromRule(rule)).flatMap((fieldResults) => Object.entries(fieldResults)).flatMap(([name, validation]) => {
1058
+ let fieldType = fieldTypes[name];
1059
+ return normalizeValidationRules({
1060
+ ...fieldType,
1061
+ validation
1062
+ }).map(addUnknownFieldsValidator).map((subRule) => {
1063
+ let nestedValue = isRecord(value) ? value[name] : void 0, nestedHidden = resolveHiddenForType(fieldType, nestedValue, value, path.concat(name), hidden);
1064
+ return defer(() => subRule.validate(nestedValue, {
1065
+ ...restOfContext,
1066
+ parent: value,
1067
+ path: path.concat(name),
1068
+ type: fieldType,
1069
+ environment,
1070
+ hidden: nestedHidden,
1071
+ __internal: { customValidationConcurrencyLimiter }
1072
+ }));
1073
+ });
1074
+ })), nestedChecks = nestedChecks.concat(type.fields.map((field) => validateItemObservable({
1075
+ ...restOfContext,
1076
+ hidden,
1077
+ parent: value,
1078
+ value: isRecord(value) ? value[field.name] : void 0,
1079
+ path: path.concat(field.name),
1080
+ type: field.type,
1081
+ environment,
1082
+ customValidationConcurrencyLimiter
1083
+ })));
1084
+ }
1085
+ return type?.jsonType === "array" && Array.isArray(value) && (nestedChecks = nestedChecks.concat(value.map((item, index) => validateItemObservable({
1086
+ ...restOfContext,
1087
+ hidden,
1088
+ parent: value,
1089
+ value: item,
1090
+ path: path.concat(isKeyedObject(item) ? { _key: item._key } : index),
1091
+ type: resolveTypeForArrayItem(item, type.of),
1092
+ environment,
1093
+ customValidationConcurrencyLimiter
1094
+ })))), 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
+ }
1096
+ function deduplicateMarkers(markers) {
1097
+ let buckets = /* @__PURE__ */ new Map();
1098
+ return markers.filter((marker) => {
1099
+ let key = JSON.stringify([
1100
+ marker.level,
1101
+ marker.code,
1102
+ marker.message,
1103
+ marker.path
1104
+ ]), bucket = buckets.get(key);
1105
+ return bucket ? !bucket.some((existing) => isEqual(existing, marker)) && (bucket.push(marker), !0) : (buckets.set(key, [marker]), !0);
1106
+ });
1107
+ }
1108
+ function hasValidationMarkerCode(marker) {
1109
+ return typeof marker.code == "string";
1110
+ }
1111
+ function toDocumentValidationMarker(marker) {
1112
+ return hasValidationMarkerCode(marker) ? marker : {
1113
+ ...marker,
1114
+ code: validationMarkerCodes.validationFailed
1115
+ };
1116
+ }
1117
+ function idle(timeout) {
1118
+ return new Observable((observer) => {
1119
+ let handle = requestIdleCallback(() => {
1120
+ observer.complete();
1121
+ }, timeout ? { timeout } : void 0);
1122
+ return () => cancelIdleCallback(handle);
1123
+ });
1124
+ }
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 };
1126
+
1127
+ //# sourceMappingURL=validateDocument-Cq33kUmN.js.map