@sanity/validation 2.33.4-shopify.8 → 2.34.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.
@@ -10,4 +10,5 @@ declare const _default: {
10
10
  };
11
11
  export default _default;
12
12
  export { RuleClass as Rule, validateDocument, inferFromSchema, inferFromSchemaType };
13
+ export { validateDocumentObservable } from './validateDocument';
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,QAAQ,CAAA;AAC9B,OAAO,gBAAgB,MAAM,oBAAoB,CAAA;AACjD,OAAO,eAAe,MAAM,mBAAmB,CAAA;AAC/C,OAAO,mBAAmB,MAAM,uBAAuB,CAAA;;;;;;;AAEvD,wBAAwF;AACxF,OAAO,EAAC,SAAS,IAAI,IAAI,EAAE,gBAAgB,EAAE,eAAe,EAAE,mBAAmB,EAAC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,QAAQ,CAAA;AAC9B,OAAO,gBAAgB,MAAM,oBAAoB,CAAA;AACjD,OAAO,eAAe,MAAM,mBAAmB,CAAA;AAC/C,OAAO,mBAAmB,MAAM,uBAAuB,CAAA;;;;;;;AAEvD,wBAAwF;AACxF,OAAO,EAAC,SAAS,IAAI,IAAI,EAAE,gBAAgB,EAAE,eAAe,EAAE,mBAAmB,EAAC,CAAA;AAElF,OAAO,EAAC,0BAA0B,EAAC,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export declare const requestIdleCallback: ((callback: IdleRequestCallback, options?: IdleRequestOptions | undefined) => number) & typeof globalThis.requestIdleCallback;
2
+ export declare const cancelIdleCallback: ((handle: number) => void) & typeof globalThis.cancelIdleCallback;
3
+ //# sourceMappingURL=requestIdleCallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requestIdleCallback.d.ts","sourceRoot":"","sources":["../../../src/util/requestIdleCallback.ts"],"names":[],"mappings":"AA2BA,eAAO,MAAM,mBAAmB,+HAAwD,CAAA;AACxF,eAAO,MAAM,kBAAkB,mEAAsD,CAAA"}
@@ -1,9 +1,11 @@
1
1
  import { SanityDocument, Schema, SchemaType, ValidationContext, ValidationMarker } from '@sanity/types';
2
+ import { Observable } from 'rxjs';
2
3
  /**
3
4
  * @internal
4
5
  */
5
6
  export declare function resolveTypeForArrayItem(item: unknown, candidates: SchemaType[]): SchemaType | undefined;
6
7
  export default function validateDocument(doc: SanityDocument, schema: Schema, context?: Pick<ValidationContext, 'getDocumentExists'>): Promise<ValidationMarker[]>;
8
+ export declare function validateDocumentObservable(doc: SanityDocument, schema: Schema, context?: Pick<ValidationContext, 'getDocumentExists'>): Observable<ValidationMarker[]>;
7
9
  /**
8
10
  * this is used make optional properties required by replacing optionals with
9
11
  * `T[P] | undefined`. this is used to prevent errors in `validateItem` where
@@ -17,6 +19,6 @@ declare type ExplicitUndefined<T> = {
17
19
  declare type ValidateItemOptions = {
18
20
  value: unknown;
19
21
  } & ExplicitUndefined<ValidationContext>;
20
- export declare function validateItem({ value, type, path, parent, ...restOfContext }: ValidateItemOptions): Promise<ValidationMarker[]>;
22
+ export declare function validateItem(opts: ValidateItemOptions): Promise<ValidationMarker[]>;
21
23
  export {};
22
24
  //# sourceMappingURL=validateDocument.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validateDocument.d.ts","sourceRoot":"","sources":["../../src/validateDocument.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,MAAM,EACN,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAMjB,MAAM,eAAe,CAAA;AAYtB;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,UAAU,EAAE,GACvB,UAAU,GAAG,SAAS,CAiBxB;AAED,wBAA8B,gBAAgB,CAC5C,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,GACrD,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA2B7B;AAED;;;;;;GAMG;AACH,aAAK,iBAAiB,CAAC,CAAC,IAAI;KACzB,CAAC,IAAI,MAAM,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS;CAC5F,CAAA;AAED,aAAK,mBAAmB,GAAG;IACzB,KAAK,EAAE,OAAO,CAAA;CACf,GAAG,iBAAiB,CAAC,iBAAiB,CAAC,CAAA;AAExC,wBAAsB,YAAY,CAAC,EACjC,KAAK,EACL,IAAI,EACJ,IAAS,EACT,MAAM,EACN,GAAG,aAAa,EACjB,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA4HnD"}
1
+ {"version":3,"file":"validateDocument.d.ts","sourceRoot":"","sources":["../../src/validateDocument.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,cAAc,EACd,MAAM,EACN,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAuB,UAAU,EAAK,MAAM,MAAM,CAAA;AAczD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,UAAU,EAAE,GACvB,UAAU,GAAG,SAAS,CAiBxB;AAGD,MAAM,CAAC,OAAO,UAAU,gBAAgB,CACtC,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,GACrD,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAE7B;AAED,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,GACrD,UAAU,CAAC,gBAAgB,EAAE,CAAC,CA2BhC;AAED;;;;;;GAMG;AACH,aAAK,iBAAiB,CAAC,CAAC,IAAI;KACzB,CAAC,IAAI,MAAM,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS;CAC5F,CAAA;AAED,aAAK,mBAAmB,GAAG;IACzB,KAAK,EAAE,OAAO,CAAA;CACf,GAAG,iBAAiB,CAAC,iBAAiB,CAAC,CAAA;AAExC,wBAAgB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAEnF"}
package/lib/index.js CHANGED
@@ -28,15 +28,25 @@ Object.defineProperty(exports, "validateDocument", {
28
28
  return _validateDocument.default;
29
29
  }
30
30
  });
31
+ Object.defineProperty(exports, "validateDocumentObservable", {
32
+ enumerable: true,
33
+ get: function get() {
34
+ return _validateDocument.validateDocumentObservable;
35
+ }
36
+ });
31
37
 
32
38
  var _Rule = _interopRequireDefault(require("./Rule"));
33
39
 
34
- var _validateDocument = _interopRequireDefault(require("./validateDocument"));
40
+ var _validateDocument = _interopRequireWildcard(require("./validateDocument"));
35
41
 
36
42
  var _inferFromSchema = _interopRequireDefault(require("./inferFromSchema"));
37
43
 
38
44
  var _inferFromSchemaType = _interopRequireDefault(require("./inferFromSchemaType"));
39
45
 
46
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
47
+
48
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
49
+
40
50
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
41
51
 
42
52
  var _default = {
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.requestIdleCallback = exports.cancelIdleCallback = void 0;
7
+
8
+ /**
9
+ * Simple requestIdleCallback polyfill
10
+ * Can be removed when all browsers support requestIdleCallback: https://caniuse.com/requestidlecallback
11
+ * @param callback
12
+ * @param options
13
+ */
14
+ var requestIdleCallbackShim = function requestIdleCallbackShim(callback, options) {
15
+ var start = Date.now();
16
+ return window.setTimeout(() => {
17
+ callback({
18
+ didTimeout: false,
19
+
20
+ timeRemaining() {
21
+ return Math.max(0, Date.now() - start);
22
+ }
23
+
24
+ });
25
+ }, 0);
26
+ };
27
+
28
+ var cancelIdleCallbackShim = function cancelIdleCallbackShim(handle) {
29
+ return window.clearTimeout(handle);
30
+ };
31
+
32
+ var requestIdleCallback = window.requestIdleCallback || requestIdleCallbackShim;
33
+ exports.requestIdleCallback = requestIdleCallback;
34
+ var cancelIdleCallback = window.cancelIdleCallback || cancelIdleCallbackShim;
35
+ exports.cancelIdleCallback = cancelIdleCallback;
@@ -5,14 +5,23 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = validateDocument;
7
7
  exports.resolveTypeForArrayItem = resolveTypeForArrayItem;
8
+ exports.validateDocumentObservable = validateDocumentObservable;
8
9
  exports.validateItem = validateItem;
9
10
 
10
11
  var _uniqBy2 = _interopRequireDefault(require("lodash/uniqBy"));
11
12
 
13
+ var _flatten2 = _interopRequireDefault(require("lodash/flatten"));
14
+
12
15
  var _types = require("@sanity/types");
13
16
 
17
+ var _rxjs = require("rxjs");
18
+
19
+ var _operators = require("rxjs/operators");
20
+
14
21
  var _typeString = _interopRequireDefault(require("./util/typeString"));
15
22
 
23
+ var _requestIdleCallback = require("./util/requestIdleCallback");
24
+
16
25
  var _ValidationError = _interopRequireDefault(require("./ValidationError"));
17
26
 
18
27
  var _normalizeValidationRules = _interopRequireDefault(require("./util/normalizeValidationRules"));
@@ -43,10 +52,6 @@ function _objectWithoutProperties(source, excluded) { if (source == null) return
43
52
 
44
53
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
45
54
 
46
- function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
47
-
48
- function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
49
-
50
55
  var isRecord = maybeRecord => typeof maybeRecord === 'object' && maybeRecord !== null && !Array.isArray(maybeRecord);
51
56
 
52
57
  var isNonNullable = value => value !== null && value !== undefined;
@@ -74,8 +79,36 @@ function resolveTypeForArrayItem(item, candidates) {
74
79
  }) || candidates.find(candidate => candidate.name === itemType) || candidates.find(candidate => candidate.name === 'object' && primitive === 'object');
75
80
  }
76
81
 
77
- function validateDocument(_x, _x2, _x3) {
78
- return _validateDocument.apply(this, arguments);
82
+ var EMPTY_MARKERS = [];
83
+
84
+ function validateDocument(doc, schema, context) {
85
+ return validateDocumentObservable(doc, schema, context).toPromise();
86
+ }
87
+
88
+ function validateDocumentObservable(doc, schema, context) {
89
+ var documentType = schema.get(doc._type);
90
+
91
+ if (!documentType) {
92
+ console.warn('Schema type for object type "%s" not found, skipping validation', doc._type);
93
+ return (0, _rxjs.of)(EMPTY_MARKERS);
94
+ }
95
+
96
+ return validateItemObservable({
97
+ parent: undefined,
98
+ value: doc,
99
+ path: [],
100
+ document: doc,
101
+ type: documentType,
102
+ getDocumentExists: context === null || context === void 0 ? void 0 : context.getDocumentExists
103
+ }).pipe((0, _operators.catchError)(err => {
104
+ console.error(err);
105
+ return (0, _rxjs.of)([{
106
+ type: 'validation',
107
+ level: 'error',
108
+ path: [],
109
+ item: new _ValidationError.default(err === null || err === void 0 ? void 0 : err.message)
110
+ }]);
111
+ }));
79
112
  }
80
113
  /**
81
114
  * this is used make optional properties required by replacing optionals with
@@ -86,146 +119,124 @@ function validateDocument(_x, _x2, _x3) {
86
119
  */
87
120
 
88
121
 
89
- function _validateDocument() {
90
- _validateDocument = _asyncToGenerator(function* (doc, schema, context) {
91
- var documentType = schema.get(doc._type);
92
-
93
- if (!documentType) {
94
- console.warn('Schema type for object type "%s" not found, skipping validation', doc._type);
95
- return [];
96
- }
97
-
98
- try {
99
- return yield validateItem({
100
- parent: undefined,
101
- value: doc,
102
- path: [],
103
- document: doc,
104
- type: documentType,
105
- getDocumentExists: context === null || context === void 0 ? void 0 : context.getDocumentExists
106
- });
107
- } catch (err) {
108
- console.error(err);
109
- return [{
110
- type: 'validation',
111
- level: 'error',
112
- path: [],
113
- item: new _ValidationError.default(err === null || err === void 0 ? void 0 : err.message)
114
- }];
115
- }
116
- });
117
- return _validateDocument.apply(this, arguments);
118
- }
119
-
120
- function validateItem(_x4) {
121
- return _validateItem.apply(this, arguments);
122
+ function validateItem(opts) {
123
+ return validateItemObservable(opts).toPromise();
122
124
  }
123
125
 
124
- function _validateItem() {
125
- _validateItem = _asyncToGenerator(function* (_ref) {
126
- var value = _ref.value,
127
- type = _ref.type,
128
- _ref$path = _ref.path,
129
- path = _ref$path === void 0 ? [] : _ref$path,
130
- parent = _ref.parent,
131
- restOfContext = _objectWithoutProperties(_ref, _excluded);
132
-
133
- var rules = (0, _normalizeValidationRules.default)(type); // run validation for the current value
134
-
135
- var selfChecks = rules.map(rule => rule.validate(value, _objectSpread(_objectSpread({}, restOfContext), {}, {
136
- parent,
137
- path,
138
- type
139
- }))); // run validation for nested values (conditionally)
140
-
141
- var nestedChecks = [];
142
- var selfIsRequired = rules.some(rule => rule.isRequired());
143
- var shouldRunNestedObjectValidation = // run nested validation for objects
144
- (type === null || type === void 0 ? void 0 : type.jsonType) === 'object' && ( // if the value is truthy
145
- !!value || // or
146
- // (the value is null or undefined) and the top-level value is required
147
- (value === null || value === undefined) && selfIsRequired);
148
-
149
- if (shouldRunNestedObjectValidation) {
150
- var fieldTypes = type.fields.reduce((acc, field) => {
151
- acc[field.name] = field.type;
152
- return acc;
153
- }, {}); // Validation for rules set at the object level with `Rule.fields({/* ... */})`
154
-
155
- nestedChecks = nestedChecks.concat(rules.map(rule => rule._fieldRules).filter(isNonNullable).flatMap(fieldResults => Object.entries(fieldResults)).flatMap(_ref2 => {
156
- var _ref3 = _slicedToArray(_ref2, 2),
157
- name = _ref3[0],
158
- validation = _ref3[1];
159
-
160
- var fieldType = fieldTypes[name];
161
- return (0, _normalizeValidationRules.default)(_objectSpread(_objectSpread({}, fieldType), {}, {
162
- validation
163
- })).map(subRule => {
164
- var nestedValue = isRecord(value) ? value[name] : undefined;
165
- return subRule.validate(nestedValue, _objectSpread(_objectSpread({}, restOfContext), {}, {
166
- parent: value,
167
- path: path.concat(name),
168
- type: fieldType
169
- }));
170
- });
171
- })); // Validation from each field's schema `validation: Rule => {/* ... */}` function
172
-
173
- nestedChecks = nestedChecks.concat(type.fields.map(field => validateItem(_objectSpread(_objectSpread({}, restOfContext), {}, {
174
- parent: value,
175
- value: isRecord(value) ? value[field.name] : undefined,
176
- path: path.concat(field.name),
177
- type: field.type
178
- }))));
179
- } // note: unlike objects, arrays should not run nested validation for undefined
180
- // values because we won't have a valid path to put a marker (i.e. missing the
181
- // key or index in the path) and the downstream form builder won't have a
182
- // valid target component
183
-
184
-
185
- var shouldRunNestedValidationForArrays = (type === null || type === void 0 ? void 0 : type.jsonType) === 'array' && Array.isArray(value);
186
-
187
- if (shouldRunNestedValidationForArrays) {
188
- nestedChecks = nestedChecks.concat(value.map(item => validateItem(_objectSpread(_objectSpread({}, restOfContext), {}, {
189
- parent: value,
190
- value: item,
191
- path: path.concat((0, _types.isKeyedObject)(item) ? {
192
- _key: item._key
193
- } : value.indexOf(item)),
194
- type: resolveTypeForArrayItem(item, type.of)
195
- }))));
196
- } // markDefs also do no run nested validation if the parent object is undefined
197
- // for a similar reason to arrays
198
-
199
-
200
- var shouldRunNestedValidationForMarkDefs = (0, _types.isBlock)(value) && value.markDefs.length && (0, _types.isBlockSchemaType)(type);
201
-
202
- if (shouldRunNestedValidationForMarkDefs) {
203
- var _type$fields = _slicedToArray(type.fields, 1),
204
- spanChildrenField = _type$fields[0];
205
-
206
- var spanType = spanChildrenField.type.of.find(_types.isSpanSchemaType);
207
- var annotations = ((spanType === null || spanType === void 0 ? void 0 : spanType.annotations) || []).reduce((map, annotationType) => {
208
- map.set(annotationType.name, annotationType);
209
- return map;
210
- }, new Map());
211
- nestedChecks = nestedChecks.concat(value.markDefs.map(markDef => validateItem(_objectSpread(_objectSpread({}, restOfContext), {}, {
212
- parent: value,
213
- value: markDef,
214
- path: path.concat(['markDefs', {
215
- _key: markDef._key
216
- }]),
217
- type: annotations.get(markDef._type)
218
- }))));
219
- }
126
+ function validateItemObservable(_ref) {
127
+ var value = _ref.value,
128
+ type = _ref.type,
129
+ _ref$path = _ref.path,
130
+ path = _ref$path === void 0 ? [] : _ref$path,
131
+ parent = _ref.parent,
132
+ restOfContext = _objectWithoutProperties(_ref, _excluded);
133
+
134
+ var rules = (0, _normalizeValidationRules.default)(type); // run validation for the current value
135
+
136
+ var selfChecks = rules.map(rule => (0, _rxjs.defer)(() => rule.validate(value, _objectSpread(_objectSpread({}, restOfContext), {}, {
137
+ parent,
138
+ path,
139
+ type
140
+ })))); // run validation for nested values (conditionally)
141
+
142
+ var fieldChecks = [];
143
+ var selfIsRequired = rules.some(rule => rule.isRequired());
144
+ var shouldRunNestedObjectValidation = // run nested validation for objects
145
+ (type === null || type === void 0 ? void 0 : type.jsonType) === 'object' && ( // if the value is truthy
146
+ !!value || // or
147
+ // (the value is null or undefined) and the top-level value is required
148
+ (value === null || value === undefined) && selfIsRequired);
149
+
150
+ if (shouldRunNestedObjectValidation) {
151
+ var fieldTypes = type.fields.reduce((acc, field) => {
152
+ acc[field.name] = field.type;
153
+ return acc;
154
+ }, {}); // Validation for rules set at the object level with `Rule.fields({/* ... */})`
155
+
156
+ fieldChecks = fieldChecks.concat(rules.map(rule => rule._fieldRules).filter(isNonNullable).flatMap(fieldResults => Object.entries(fieldResults)).flatMap(_ref2 => {
157
+ var _ref3 = _slicedToArray(_ref2, 2),
158
+ name = _ref3[0],
159
+ validation = _ref3[1];
160
+
161
+ var fieldType = fieldTypes[name];
162
+ return (0, _normalizeValidationRules.default)(_objectSpread(_objectSpread({}, fieldType), {}, {
163
+ validation
164
+ })).map(subRule => {
165
+ var nestedValue = isRecord(value) ? value[name] : undefined;
166
+ return (0, _rxjs.defer)(() => subRule.validate(nestedValue, _objectSpread(_objectSpread({}, restOfContext), {}, {
167
+ parent: value,
168
+ path: path.concat(name),
169
+ type: fieldType
170
+ })));
171
+ });
172
+ })); // Validation from each field's schema `validation: Rule => {/* ... */}` function
173
+
174
+ fieldChecks = fieldChecks.concat(type.fields.map(field => validateItemObservable(_objectSpread(_objectSpread({}, restOfContext), {}, {
175
+ parent: value,
176
+ value: isRecord(value) ? value[field.name] : undefined,
177
+ path: path.concat(field.name),
178
+ type: field.type
179
+ }))));
180
+ } // note: unlike objects, arrays should not run nested validation for undefined
181
+ // values because we won't have a valid path to put a marker (i.e. missing the
182
+ // key or index in the path) and the downstream form builder won't have a
183
+ // valid target component
184
+
185
+
186
+ var shouldRunNestedValidationForArrays = (type === null || type === void 0 ? void 0 : type.jsonType) === 'array' && Array.isArray(value);
187
+
188
+ if (shouldRunNestedValidationForArrays) {
189
+ fieldChecks = fieldChecks.concat(value.map(item => validateItemObservable(_objectSpread(_objectSpread({}, restOfContext), {}, {
190
+ parent: value,
191
+ value: item,
192
+ path: path.concat((0, _types.isKeyedObject)(item) ? {
193
+ _key: item._key
194
+ } : value.indexOf(item)),
195
+ type: resolveTypeForArrayItem(item, type.of)
196
+ }))));
197
+ } // markDefs also do no run nested validation if the parent object is undefined
198
+ // for a similar reason to arrays
199
+
200
+
201
+ var shouldRunNestedValidationForMarkDefs = (0, _types.isBlock)(value) && value.markDefs.length && (0, _types.isBlockSchemaType)(type);
202
+
203
+ if (shouldRunNestedValidationForMarkDefs) {
204
+ var _type$fields = _slicedToArray(type.fields, 1),
205
+ spanChildrenField = _type$fields[0];
206
+
207
+ var spanType = spanChildrenField.type.of.find(_types.isSpanSchemaType);
208
+ var annotations = ((spanType === null || spanType === void 0 ? void 0 : spanType.annotations) || []).reduce((acc, annotationType) => {
209
+ acc.set(annotationType.name, annotationType);
210
+ return acc;
211
+ }, new Map());
212
+ fieldChecks = fieldChecks.concat(value.markDefs.map(markDef => validateItemObservable(_objectSpread(_objectSpread({}, restOfContext), {}, {
213
+ parent: value,
214
+ value: markDef,
215
+ path: path.concat(['markDefs', {
216
+ _key: markDef._key
217
+ }]),
218
+ type: annotations.get(markDef._type)
219
+ }))));
220
+ }
220
221
 
221
- var results = (yield Promise.all([...selfChecks, ...nestedChecks])).flat(); // run `uniqBy` if `_fieldRules` are present because they can
222
+ return (0, _rxjs.defer)(() => (0, _rxjs.merge)([...selfChecks, ...fieldChecks])).pipe((0, _operators.mergeMap)(validateNode => (0, _rxjs.concat)(idle(), validateNode), 40), (0, _operators.mergeAll)(), (0, _operators.toArray)(), (0, _operators.map)(_flatten2.default), (0, _operators.map)(results => {
223
+ // run `uniqBy` if `_fieldRules` are present because they can
222
224
  // cause repeat markers
223
-
224
225
  if (rules.some(rule => rule._fieldRules)) {
225
226
  return (0, _uniqBy2.default)(results, rule => JSON.stringify(rule));
226
227
  }
227
228
 
228
229
  return results;
230
+ }));
231
+ }
232
+
233
+ function idle(timeout) {
234
+ return new _rxjs.Observable(observer => {
235
+ var handle = (0, _requestIdleCallback.requestIdleCallback)(() => {
236
+ observer.complete();
237
+ }, timeout ? {
238
+ timeout
239
+ } : undefined);
240
+ return () => (0, _requestIdleCallback.cancelIdleCallback)(handle);
229
241
  });
230
- return _validateItem.apply(this, arguments);
231
242
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/validation",
3
- "version": "2.33.4-shopify.8+b3097f8c9",
3
+ "version": "2.34.0",
4
4
  "description": "Validation and warning infrastructure for Sanity projects",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./dist/dts",
@@ -33,12 +33,13 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@sanity/client": "^3.3.3",
36
- "@sanity/schema": "2.33.4-shopify.8+b3097f8c9"
36
+ "@sanity/schema": "2.33.2"
37
37
  },
38
38
  "dependencies": {
39
- "@sanity/types": "2.33.4-shopify.8+b3097f8c9",
39
+ "@sanity/types": "2.34.0",
40
40
  "date-fns": "^2.16.1",
41
- "lodash": "^4.17.15"
41
+ "lodash": "^4.17.15",
42
+ "rxjs": "^6.5.3"
42
43
  },
43
- "gitHead": "b3097f8c9ec8d874b3fd86005a17b0cb5eaf136c"
44
+ "gitHead": "271991702d6db370f2157232d738445e968ee546"
44
45
  }
package/src/index.ts CHANGED
@@ -5,3 +5,5 @@ import inferFromSchemaType from './inferFromSchemaType'
5
5
 
6
6
  export default {Rule: RuleClass, validateDocument, inferFromSchema, inferFromSchemaType}
7
7
  export {RuleClass as Rule, validateDocument, inferFromSchema, inferFromSchemaType}
8
+
9
+ export {validateDocumentObservable} from './validateDocument'
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Simple requestIdleCallback polyfill
3
+ * Can be removed when all browsers support requestIdleCallback: https://caniuse.com/requestidlecallback
4
+ * @param callback
5
+ * @param options
6
+ */
7
+ const requestIdleCallbackShim: typeof window.requestIdleCallback = function requestIdleCallbackShim(
8
+ callback,
9
+ options?
10
+ ): number {
11
+ const start = Date.now()
12
+ return window.setTimeout(() => {
13
+ callback({
14
+ didTimeout: false,
15
+ timeRemaining() {
16
+ return Math.max(0, Date.now() - start)
17
+ },
18
+ })
19
+ }, 0)
20
+ }
21
+
22
+ const cancelIdleCallbackShim: typeof window.cancelIdleCallback = function cancelIdleCallbackShim(
23
+ handle: number
24
+ ): void {
25
+ return window.clearTimeout(handle)
26
+ }
27
+
28
+ export const requestIdleCallback = window.requestIdleCallback || requestIdleCallbackShim
29
+ export const cancelIdleCallback = window.cancelIdleCallback || cancelIdleCallbackShim
@@ -687,81 +687,4 @@ describe('validateItem', () => {
687
687
  },
688
688
  ])
689
689
  })
690
-
691
- it('runs all nested validation checks concurrently', async () => {
692
- type Resolver = (value: true) => void
693
- const resolvers = new Set<Resolver>()
694
- const customValidationFn = () => new Promise<true>((resolve) => resolvers.add(resolve))
695
-
696
- const schema: Schema = createSchema({
697
- types: [
698
- {
699
- name: 'root',
700
- type: 'object',
701
- fields: [
702
- {
703
- name: 'level1Object',
704
- type: 'object',
705
- validation: (rule: Rule) => [
706
- rule.custom(customValidationFn),
707
- rule.fields({
708
- level2String: (r) => r.custom(customValidationFn),
709
- }),
710
- ],
711
- fields: [
712
- {
713
- name: 'level2String',
714
- type: 'string',
715
- validation: (rule: Rule) => rule.custom(customValidationFn),
716
- },
717
- ],
718
- },
719
- {
720
- name: 'level1Array',
721
- type: 'array',
722
- validation: (rule: Rule) => rule.custom(customValidationFn),
723
- of: [
724
- {
725
- type: 'object',
726
- fields: [
727
- {
728
- name: 'level2Number',
729
- type: 'number',
730
- validation: (rule: Rule) => rule.custom(customValidationFn),
731
- },
732
- ],
733
- },
734
- ],
735
- },
736
- ],
737
- validation: (rule: Rule) => rule.custom(customValidationFn),
738
- },
739
- ],
740
- })
741
-
742
- const value = {
743
- level1Object: {level3String: 'a string'},
744
- level1Array: [{level2Number: 5}],
745
- }
746
-
747
- const resultPromise = validateItem({
748
- value,
749
- document: undefined,
750
- parent: undefined,
751
- path: undefined,
752
- type: schema.get('root'),
753
- getDocumentExists: undefined,
754
- })
755
-
756
- // after `validateItem(...)` has been initiated, all of the custom
757
- // validation calls should have fired so the resolver size should equal the
758
- // amount of total fields
759
- expect(resolvers.size).toBe(6)
760
-
761
- for (const resolver of resolvers) {
762
- resolver(true)
763
- }
764
-
765
- await resultPromise
766
- })
767
690
  })
@@ -1,17 +1,20 @@
1
1
  import {
2
+ isBlock,
3
+ isBlockSchemaType,
4
+ isKeyedObject,
5
+ isSpanSchemaType,
6
+ isTypedObject,
2
7
  SanityDocument,
3
8
  Schema,
4
9
  SchemaType,
5
10
  ValidationContext,
6
11
  ValidationMarker,
7
- isKeyedObject,
8
- isTypedObject,
9
- isBlock,
10
- isBlockSchemaType,
11
- isSpanSchemaType,
12
12
  } from '@sanity/types'
13
- import {uniqBy} from 'lodash'
13
+ import {concat, defer, merge, Observable, of} from 'rxjs'
14
+ import {catchError, map, mergeAll, mergeMap, toArray} from 'rxjs/operators'
15
+ import {flatten, uniqBy} from 'lodash'
14
16
  import typeString from './util/typeString'
17
+ import {requestIdleCallback, cancelIdleCallback} from './util/requestIdleCallback'
15
18
  import ValidationErrorClass from './ValidationError'
16
19
  import normalizeValidationRules from './util/normalizeValidationRules'
17
20
 
@@ -45,38 +48,47 @@ export function resolveTypeForArrayItem(
45
48
  candidates.find((candidate) => candidate.name === 'object' && primitive === 'object')
46
49
  )
47
50
  }
51
+ const EMPTY_MARKERS: ValidationMarker[] = []
48
52
 
49
- export default async function validateDocument(
53
+ export default function validateDocument(
50
54
  doc: SanityDocument,
51
55
  schema: Schema,
52
56
  context?: Pick<ValidationContext, 'getDocumentExists'>
53
57
  ): Promise<ValidationMarker[]> {
58
+ return validateDocumentObservable(doc, schema, context).toPromise()
59
+ }
60
+
61
+ export function validateDocumentObservable(
62
+ doc: SanityDocument,
63
+ schema: Schema,
64
+ context?: Pick<ValidationContext, 'getDocumentExists'>
65
+ ): Observable<ValidationMarker[]> {
54
66
  const documentType = schema.get(doc._type)
55
67
  if (!documentType) {
56
68
  console.warn('Schema type for object type "%s" not found, skipping validation', doc._type)
57
- return []
69
+ return of(EMPTY_MARKERS)
58
70
  }
59
71
 
60
- try {
61
- return await validateItem({
62
- parent: undefined,
63
- value: doc,
64
- path: [],
65
- document: doc,
66
- type: documentType,
67
- getDocumentExists: context?.getDocumentExists,
72
+ return validateItemObservable({
73
+ parent: undefined,
74
+ value: doc,
75
+ path: [],
76
+ document: doc,
77
+ type: documentType,
78
+ getDocumentExists: context?.getDocumentExists,
79
+ }).pipe(
80
+ catchError((err) => {
81
+ console.error(err)
82
+ return of([
83
+ {
84
+ type: 'validation' as const,
85
+ level: 'error' as const,
86
+ path: [],
87
+ item: new ValidationErrorClass(err?.message),
88
+ },
89
+ ])
68
90
  })
69
- } catch (err) {
70
- console.error(err)
71
- return [
72
- {
73
- type: 'validation',
74
- level: 'error',
75
- path: [],
76
- item: new ValidationErrorClass(err?.message),
77
- },
78
- ]
79
- }
91
+ )
80
92
  }
81
93
 
82
94
  /**
@@ -94,27 +106,32 @@ type ValidateItemOptions = {
94
106
  value: unknown
95
107
  } & ExplicitUndefined<ValidationContext>
96
108
 
97
- export async function validateItem({
109
+ export function validateItem(opts: ValidateItemOptions): Promise<ValidationMarker[]> {
110
+ return validateItemObservable(opts).toPromise()
111
+ }
112
+
113
+ function validateItemObservable({
98
114
  value,
99
115
  type,
100
116
  path = [],
101
117
  parent,
102
118
  ...restOfContext
103
- }: ValidateItemOptions): Promise<ValidationMarker[]> {
119
+ }: ValidateItemOptions): Observable<ValidationMarker[]> {
104
120
  const rules = normalizeValidationRules(type)
105
-
106
121
  // run validation for the current value
107
122
  const selfChecks = rules.map((rule) =>
108
- rule.validate(value, {
109
- ...restOfContext,
110
- parent,
111
- path,
112
- type,
113
- })
123
+ defer(() =>
124
+ rule.validate(value, {
125
+ ...restOfContext,
126
+ parent,
127
+ path,
128
+ type,
129
+ })
130
+ )
114
131
  )
115
132
 
116
133
  // run validation for nested values (conditionally)
117
- let nestedChecks: Array<Promise<ValidationMarker[]>> = []
134
+ let fieldChecks: Array<Observable<ValidationMarker[]>> = []
118
135
 
119
136
  const selfIsRequired = rules.some((rule) => rule.isRequired())
120
137
  const shouldRunNestedObjectValidation =
@@ -132,7 +149,7 @@ export async function validateItem({
132
149
  }, {})
133
150
 
134
151
  // Validation for rules set at the object level with `Rule.fields({/* ... */})`
135
- nestedChecks = nestedChecks.concat(
152
+ fieldChecks = fieldChecks.concat(
136
153
  rules
137
154
  .map((rule) => rule._fieldRules)
138
155
  .filter(isNonNullable)
@@ -141,20 +158,22 @@ export async function validateItem({
141
158
  const fieldType = fieldTypes[name]
142
159
  return normalizeValidationRules({...fieldType, validation}).map((subRule) => {
143
160
  const nestedValue = isRecord(value) ? value[name] : undefined
144
- return subRule.validate(nestedValue, {
145
- ...restOfContext,
146
- parent: value,
147
- path: path.concat(name),
148
- type: fieldType,
149
- })
161
+ return defer(() =>
162
+ subRule.validate(nestedValue, {
163
+ ...restOfContext,
164
+ parent: value,
165
+ path: path.concat(name),
166
+ type: fieldType,
167
+ })
168
+ )
150
169
  })
151
170
  })
152
171
  )
153
172
 
154
173
  // Validation from each field's schema `validation: Rule => {/* ... */}` function
155
- nestedChecks = nestedChecks.concat(
174
+ fieldChecks = fieldChecks.concat(
156
175
  type.fields.map((field) =>
157
- validateItem({
176
+ validateItemObservable({
158
177
  ...restOfContext,
159
178
  parent: value,
160
179
  value: isRecord(value) ? value[field.name] : undefined,
@@ -172,9 +191,9 @@ export async function validateItem({
172
191
  const shouldRunNestedValidationForArrays = type?.jsonType === 'array' && Array.isArray(value)
173
192
 
174
193
  if (shouldRunNestedValidationForArrays) {
175
- nestedChecks = nestedChecks.concat(
194
+ fieldChecks = fieldChecks.concat(
176
195
  value.map((item) =>
177
- validateItem({
196
+ validateItemObservable({
178
197
  ...restOfContext,
179
198
  parent: value,
180
199
  value: item,
@@ -195,16 +214,16 @@ export async function validateItem({
195
214
  const spanType = spanChildrenField.type.of.find(isSpanSchemaType)
196
215
 
197
216
  const annotations = (spanType?.annotations || []).reduce<Map<string, SchemaType>>(
198
- (map, annotationType) => {
199
- map.set(annotationType.name, annotationType)
200
- return map
217
+ (acc, annotationType) => {
218
+ acc.set(annotationType.name, annotationType)
219
+ return acc
201
220
  },
202
221
  new Map()
203
222
  )
204
223
 
205
- nestedChecks = nestedChecks.concat(
224
+ fieldChecks = fieldChecks.concat(
206
225
  value.markDefs.map((markDef) =>
207
- validateItem({
226
+ validateItemObservable({
208
227
  ...restOfContext,
209
228
  parent: value,
210
229
  value: markDef,
@@ -215,13 +234,31 @@ export async function validateItem({
215
234
  )
216
235
  }
217
236
 
218
- const results = (await Promise.all([...selfChecks, ...nestedChecks])).flat()
237
+ return defer(() => merge([...selfChecks, ...fieldChecks])).pipe(
238
+ mergeMap((validateNode) => concat(idle(), validateNode), 40),
239
+ mergeAll(),
240
+ toArray(),
241
+ map(flatten),
242
+ map((results) => {
243
+ // run `uniqBy` if `_fieldRules` are present because they can
244
+ // cause repeat markers
245
+ if (rules.some((rule) => rule._fieldRules)) {
246
+ return uniqBy(results, (rule) => JSON.stringify(rule))
247
+ }
248
+ return results
249
+ })
250
+ )
251
+ }
219
252
 
220
- // run `uniqBy` if `_fieldRules` are present because they can
221
- // cause repeat markers
222
- if (rules.some((rule) => rule._fieldRules)) {
223
- return uniqBy(results, (rule) => JSON.stringify(rule))
224
- }
253
+ function idle(timeout?: number): Observable<never> {
254
+ return new Observable<never>((observer) => {
255
+ const handle = requestIdleCallback(
256
+ () => {
257
+ observer.complete()
258
+ },
259
+ timeout ? {timeout} : undefined
260
+ )
225
261
 
226
- return results
262
+ return () => cancelIdleCallback(handle)
263
+ })
227
264
  }