@sanity/validation 2.33.3-performance-opts.4 → 2.33.3-performance-opts.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,10 @@
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
- export default function validateDocument(doc: SanityDocument, schema: Schema, context?: Pick<ValidationContext, 'getDocumentExists'>): Promise<ValidationMarker[]>;
7
+ export default function validateDocument(doc: SanityDocument, schema: Schema, context?: Pick<ValidationContext, 'getDocumentExists'>): Observable<ValidationMarker[]>;
7
8
  /**
8
9
  * this is used make optional properties required by replacing optionals with
9
10
  * `T[P] | undefined`. this is used to prevent errors in `validateItem` where
@@ -17,6 +18,6 @@ declare type ExplicitUndefined<T> = {
17
18
  declare type ValidateItemOptions = {
18
19
  value: unknown;
19
20
  } & ExplicitUndefined<ValidationContext>;
20
- export declare function validateItem({ value, type, path, parent, ...restOfContext }: ValidateItemOptions): Promise<ValidationMarker[]>;
21
+ export declare function validateItem({ value, type, path, parent, ...restOfContext }: ValidateItemOptions): Observable<ValidationMarker[]>;
21
22
  export {};
22
23
  //# 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;AAIxC,wBAAsB,YAAY,CAAC,EACjC,KAAK,EACL,IAAI,EACJ,IAAS,EACT,MAAM,EACN,GAAG,aAAa,EACjB,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA6HnD"}
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;AAuBzD;;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,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,EAC3B,KAAK,EACL,IAAI,EACJ,IAAS,EACT,MAAM,EACN,GAAG,aAAa,EACjB,EAAE,mBAAmB,GAAG,UAAU,CAAC,gBAAgB,EAAE,CAAC,CAsItD"}
@@ -9,8 +9,14 @@ exports.validateItem = validateItem;
9
9
 
10
10
  var _uniqBy2 = _interopRequireDefault(require("lodash/uniqBy"));
11
11
 
12
+ var _flatten2 = _interopRequireDefault(require("lodash/flatten"));
13
+
12
14
  var _types = require("@sanity/types");
13
15
 
16
+ var _rxjs = require("rxjs");
17
+
18
+ var _operators = require("rxjs/operators");
19
+
14
20
  var _typeString = _interopRequireDefault(require("./util/typeString"));
15
21
 
16
22
  var _ValidationError = _interopRequireDefault(require("./ValidationError"));
@@ -43,10 +49,6 @@ function _objectWithoutProperties(source, excluded) { if (source == null) return
43
49
 
44
50
  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
51
 
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
52
  var isRecord = maybeRecord => typeof maybeRecord === 'object' && maybeRecord !== null && !Array.isArray(maybeRecord);
51
53
 
52
54
  var isNonNullable = value => value !== null && value !== undefined;
@@ -74,8 +76,32 @@ function resolveTypeForArrayItem(item, candidates) {
74
76
  }) || candidates.find(candidate => candidate.name === itemType) || candidates.find(candidate => candidate.name === 'object' && primitive === 'object');
75
77
  }
76
78
 
77
- function validateDocument(_x, _x2, _x3) {
78
- return _validateDocument.apply(this, arguments);
79
+ var EMPTY_MARKERS = [];
80
+
81
+ function validateDocument(doc, schema, context) {
82
+ var documentType = schema.get(doc._type);
83
+
84
+ if (!documentType) {
85
+ console.warn('Schema type for object type "%s" not found, skipping validation', doc._type);
86
+ return (0, _rxjs.of)(EMPTY_MARKERS);
87
+ }
88
+
89
+ return validateItem({
90
+ parent: undefined,
91
+ value: doc,
92
+ path: [],
93
+ document: doc,
94
+ type: documentType,
95
+ getDocumentExists: context === null || context === void 0 ? void 0 : context.getDocumentExists
96
+ }).pipe((0, _operators.catchError)(err => {
97
+ console.error(err);
98
+ return (0, _rxjs.of)([{
99
+ type: 'validation',
100
+ level: 'error',
101
+ path: [],
102
+ item: new _ValidationError.default(err === null || err === void 0 ? void 0 : err.message)
103
+ }]);
104
+ }));
79
105
  }
80
106
  /**
81
107
  * this is used make optional properties required by replacing optionals with
@@ -86,149 +112,120 @@ function validateDocument(_x, _x2, _x3) {
86
112
  */
87
113
 
88
114
 
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
115
+ function validateItem(_ref) {
116
+ var value = _ref.value,
117
+ type = _ref.type,
118
+ _ref$path = _ref.path,
119
+ path = _ref$path === void 0 ? [] : _ref$path,
120
+ parent = _ref.parent,
121
+ restOfContext = _objectWithoutProperties(_ref, _excluded);
122
+
123
+ var rules = (0, _normalizeValidationRules.default)(type); // run validation for the current value
124
+
125
+ var selfChecks = rules.map(rule => (0, _rxjs.defer)(() => rule.validate(value, _objectSpread(_objectSpread({}, restOfContext), {}, {
126
+ parent,
127
+ path,
128
+ type
129
+ })))); // run validation for nested values (conditionally)
130
+
131
+ var fieldChecks = [];
132
+ var selfIsRequired = rules.some(rule => rule.isRequired());
133
+ var shouldRunNestedObjectValidation = // run nested validation for objects
134
+ (type === null || type === void 0 ? void 0 : type.jsonType) === 'object' && ( // if the value is truthy
135
+ !!value || // or
136
+ // (the value is null or undefined) and the top-level value is required
137
+ (value === null || value === undefined) && selfIsRequired);
138
+
139
+ if (shouldRunNestedObjectValidation) {
140
+ var fieldTypes = type.fields.reduce((acc, field) => {
141
+ acc[field.name] = field.type;
142
+ return acc;
143
+ }, {}); // Validation for rules set at the object level with `Rule.fields({/* ... */})`
144
+
145
+ fieldChecks = fieldChecks.concat(rules.map(rule => rule._fieldRules).filter(isNonNullable).flatMap(fieldResults => Object.entries(fieldResults)).flatMap(_ref2 => {
146
+ var _ref3 = _slicedToArray(_ref2, 2),
147
+ name = _ref3[0],
148
+ validation = _ref3[1];
149
+
150
+ var fieldType = fieldTypes[name];
151
+ return (0, _normalizeValidationRules.default)(_objectSpread(_objectSpread({}, fieldType), {}, {
152
+ validation
153
+ })).map(subRule => {
154
+ var nestedValue = isRecord(value) ? value[name] : undefined;
155
+ return (0, _rxjs.defer)(() => subRule.validate(nestedValue, _objectSpread(_objectSpread({}, restOfContext), {}, {
156
+ parent: value,
157
+ path: path.concat(name),
158
+ type: fieldType
159
+ })));
106
160
  });
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
- var idle = () => new Promise(resolve => window.requestIdleCallback(resolve));
121
-
122
- function validateItem(_x4) {
123
- return _validateItem.apply(this, arguments);
124
- }
125
-
126
- function _validateItem() {
127
- _validateItem = _asyncToGenerator(function* (_ref) {
128
- var value = _ref.value,
129
- type = _ref.type,
130
- _ref$path = _ref.path,
131
- path = _ref$path === void 0 ? [] : _ref$path,
132
- parent = _ref.parent,
133
- restOfContext = _objectWithoutProperties(_ref, _excluded);
134
-
135
- yield idle();
136
- var rules = (0, _normalizeValidationRules.default)(type); // run validation for the current value
137
-
138
- var selfChecks = rules.map(rule => rule.validate(value, _objectSpread(_objectSpread({}, restOfContext), {}, {
139
- parent,
140
- path,
141
- type
142
- }))); // run validation for nested values (conditionally)
143
-
144
- var nestedChecks = [];
145
- var selfIsRequired = rules.some(rule => rule.isRequired());
146
- var shouldRunNestedObjectValidation = // run nested validation for objects
147
- (type === null || type === void 0 ? void 0 : type.jsonType) === 'object' && ( // if the value is truthy
148
- !!value || // or
149
- // (the value is null or undefined) and the top-level value is required
150
- (value === null || value === undefined) && selfIsRequired);
151
-
152
- if (shouldRunNestedObjectValidation) {
153
- var fieldTypes = type.fields.reduce((acc, field) => {
154
- acc[field.name] = field.type;
155
- return acc;
156
- }, {}); // Validation for rules set at the object level with `Rule.fields({/* ... */})`
157
-
158
- nestedChecks = nestedChecks.concat(rules.map(rule => rule._fieldRules).filter(isNonNullable).flatMap(fieldResults => Object.entries(fieldResults)).flatMap(_ref2 => {
159
- var _ref3 = _slicedToArray(_ref2, 2),
160
- name = _ref3[0],
161
- validation = _ref3[1];
162
-
163
- var fieldType = fieldTypes[name];
164
- return (0, _normalizeValidationRules.default)(_objectSpread(_objectSpread({}, fieldType), {}, {
165
- validation
166
- })).map(subRule => {
167
- var nestedValue = isRecord(value) ? value[name] : undefined;
168
- return subRule.validate(nestedValue, _objectSpread(_objectSpread({}, restOfContext), {}, {
169
- parent: value,
170
- path: path.concat(name),
171
- type: fieldType
172
- }));
173
- });
174
- })); // Validation from each field's schema `validation: Rule => {/* ... */}` function
175
-
176
- nestedChecks = nestedChecks.concat(type.fields.map(field => validateItem(_objectSpread(_objectSpread({}, restOfContext), {}, {
177
- parent: value,
178
- value: isRecord(value) ? value[field.name] : undefined,
179
- path: path.concat(field.name),
180
- type: field.type
181
- }))));
182
- } // note: unlike objects, arrays should not run nested validation for undefined
183
- // values because we won't have a valid path to put a marker (i.e. missing the
184
- // key or index in the path) and the downstream form builder won't have a
185
- // valid target component
186
-
187
-
188
- var shouldRunNestedValidationForArrays = (type === null || type === void 0 ? void 0 : type.jsonType) === 'array' && Array.isArray(value);
189
-
190
- if (shouldRunNestedValidationForArrays) {
191
- nestedChecks = nestedChecks.concat(value.map(item => validateItem(_objectSpread(_objectSpread({}, restOfContext), {}, {
192
- parent: value,
193
- value: item,
194
- path: path.concat((0, _types.isKeyedObject)(item) ? {
195
- _key: item._key
196
- } : value.indexOf(item)),
197
- type: resolveTypeForArrayItem(item, type.of)
198
- }))));
199
- } // markDefs also do no run nested validation if the parent object is undefined
200
- // for a similar reason to arrays
201
-
202
-
203
- var shouldRunNestedValidationForMarkDefs = (0, _types.isBlock)(value) && value.markDefs.length && (0, _types.isBlockSchemaType)(type);
204
-
205
- if (shouldRunNestedValidationForMarkDefs) {
206
- var _type$fields = _slicedToArray(type.fields, 1),
207
- spanChildrenField = _type$fields[0];
208
-
209
- var spanType = spanChildrenField.type.of.find(_types.isSpanSchemaType);
210
- var annotations = ((spanType === null || spanType === void 0 ? void 0 : spanType.annotations) || []).reduce((map, annotationType) => {
211
- map.set(annotationType.name, annotationType);
212
- return map;
213
- }, new Map());
214
- nestedChecks = nestedChecks.concat(value.markDefs.map(markDef => validateItem(_objectSpread(_objectSpread({}, restOfContext), {}, {
215
- parent: value,
216
- value: markDef,
217
- path: path.concat(['markDefs', {
218
- _key: markDef._key
219
- }]),
220
- type: annotations.get(markDef._type)
221
- }))));
222
- }
161
+ })); // Validation from each field's schema `validation: Rule => {/* ... */}` function
162
+
163
+ fieldChecks = fieldChecks.concat(type.fields.map(field => validateItem(_objectSpread(_objectSpread({}, restOfContext), {}, {
164
+ parent: value,
165
+ value: isRecord(value) ? value[field.name] : undefined,
166
+ path: path.concat(field.name),
167
+ type: field.type
168
+ }))));
169
+ } // note: unlike objects, arrays should not run nested validation for undefined
170
+ // values because we won't have a valid path to put a marker (i.e. missing the
171
+ // key or index in the path) and the downstream form builder won't have a
172
+ // valid target component
173
+
174
+
175
+ var shouldRunNestedValidationForArrays = (type === null || type === void 0 ? void 0 : type.jsonType) === 'array' && Array.isArray(value);
176
+
177
+ if (shouldRunNestedValidationForArrays) {
178
+ fieldChecks = fieldChecks.concat(value.map(item => validateItem(_objectSpread(_objectSpread({}, restOfContext), {}, {
179
+ parent: value,
180
+ value: item,
181
+ path: path.concat((0, _types.isKeyedObject)(item) ? {
182
+ _key: item._key
183
+ } : value.indexOf(item)),
184
+ type: resolveTypeForArrayItem(item, type.of)
185
+ }))));
186
+ } // markDefs also do no run nested validation if the parent object is undefined
187
+ // for a similar reason to arrays
188
+
189
+
190
+ var shouldRunNestedValidationForMarkDefs = (0, _types.isBlock)(value) && value.markDefs.length && (0, _types.isBlockSchemaType)(type);
191
+
192
+ if (shouldRunNestedValidationForMarkDefs) {
193
+ var _type$fields = _slicedToArray(type.fields, 1),
194
+ spanChildrenField = _type$fields[0];
195
+
196
+ var spanType = spanChildrenField.type.of.find(_types.isSpanSchemaType);
197
+ var annotations = ((spanType === null || spanType === void 0 ? void 0 : spanType.annotations) || []).reduce((map, annotationType) => {
198
+ map.set(annotationType.name, annotationType);
199
+ return map;
200
+ }, new Map());
201
+ fieldChecks = fieldChecks.concat(value.markDefs.map(markDef => validateItem(_objectSpread(_objectSpread({}, restOfContext), {}, {
202
+ parent: value,
203
+ value: markDef,
204
+ path: path.concat(['markDefs', {
205
+ _key: markDef._key
206
+ }]),
207
+ type: annotations.get(markDef._type)
208
+ }))));
209
+ }
223
210
 
224
- var results = (yield Promise.all([...selfChecks, ...nestedChecks])).flat(); // run `uniqBy` if `_fieldRules` are present because they can
211
+ 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.filter)(arr => arr.length > 0), (0, _operators.map)(_flatten2.default), (0, _operators.map)(results => {
212
+ // run `uniqBy` if `_fieldRules` are present because they can
225
213
  // cause repeat markers
226
-
227
214
  if (rules.some(rule => rule._fieldRules)) {
228
215
  return (0, _uniqBy2.default)(results, rule => JSON.stringify(rule));
229
216
  }
230
217
 
231
218
  return results;
219
+ }), (0, _operators.filter)(arr => arr.length > 0));
220
+ }
221
+
222
+ function idle(timeout) {
223
+ return new _rxjs.Observable(observer => {
224
+ var handle = requestIdleCallback(() => {
225
+ observer.complete();
226
+ }, timeout ? {
227
+ timeout
228
+ } : undefined);
229
+ return () => cancelIdleCallback(handle);
232
230
  });
233
- return _validateItem.apply(this, arguments);
234
231
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/validation",
3
- "version": "2.33.3-performance-opts.4+87ec2ee6e8",
3
+ "version": "2.33.3-performance-opts.9+6ea72536ea",
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.3-performance-opts.4+87ec2ee6e8"
36
+ "@sanity/schema": "2.33.3-performance-opts.9+6ea72536ea"
37
37
  },
38
38
  "dependencies": {
39
- "@sanity/types": "2.33.3-performance-opts.4+87ec2ee6e8",
39
+ "@sanity/types": "2.33.3-performance-opts.9+6ea72536ea",
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": "87ec2ee6e85a8cb44368bde6557516e9ed024af0"
44
+ "gitHead": "6ea72536ea8c9aaebb1c57b304fc23f1e6705a67"
44
45
  }
@@ -1,16 +1,28 @@
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 {
15
+ catchError,
16
+ combineAll,
17
+ concatMap,
18
+ filter,
19
+ map,
20
+ mergeAll,
21
+ mergeMap,
22
+ tap,
23
+ toArray,
24
+ } from 'rxjs/operators'
25
+ import {flatten, uniqBy} from 'lodash'
14
26
  import typeString from './util/typeString'
15
27
  import ValidationErrorClass from './ValidationError'
16
28
  import normalizeValidationRules from './util/normalizeValidationRules'
@@ -45,38 +57,39 @@ export function resolveTypeForArrayItem(
45
57
  candidates.find((candidate) => candidate.name === 'object' && primitive === 'object')
46
58
  )
47
59
  }
60
+ const EMPTY_MARKERS: ValidationMarker[] = []
48
61
 
49
- export default async function validateDocument(
62
+ export default function validateDocument(
50
63
  doc: SanityDocument,
51
64
  schema: Schema,
52
65
  context?: Pick<ValidationContext, 'getDocumentExists'>
53
- ): Promise<ValidationMarker[]> {
66
+ ): Observable<ValidationMarker[]> {
54
67
  const documentType = schema.get(doc._type)
55
68
  if (!documentType) {
56
69
  console.warn('Schema type for object type "%s" not found, skipping validation', doc._type)
57
- return []
70
+ return of(EMPTY_MARKERS)
58
71
  }
59
72
 
60
- try {
61
- return await validateItem({
62
- parent: undefined,
63
- value: doc,
64
- path: [],
65
- document: doc,
66
- type: documentType,
67
- getDocumentExists: context?.getDocumentExists,
73
+ return validateItem({
74
+ parent: undefined,
75
+ value: doc,
76
+ path: [],
77
+ document: doc,
78
+ type: documentType,
79
+ getDocumentExists: context?.getDocumentExists,
80
+ }).pipe(
81
+ catchError((err) => {
82
+ console.error(err)
83
+ return of([
84
+ {
85
+ type: 'validation' as const,
86
+ level: 'error' as const,
87
+ path: [],
88
+ item: new ValidationErrorClass(err?.message),
89
+ },
90
+ ])
68
91
  })
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
- }
92
+ )
80
93
  }
81
94
 
82
95
  /**
@@ -94,30 +107,28 @@ type ValidateItemOptions = {
94
107
  value: unknown
95
108
  } & ExplicitUndefined<ValidationContext>
96
109
 
97
- const idle = () => new Promise((resolve) => window.requestIdleCallback(resolve))
98
-
99
- export async function validateItem({
110
+ export function validateItem({
100
111
  value,
101
112
  type,
102
113
  path = [],
103
114
  parent,
104
115
  ...restOfContext
105
- }: ValidateItemOptions): Promise<ValidationMarker[]> {
106
- await idle()
116
+ }: ValidateItemOptions): Observable<ValidationMarker[]> {
107
117
  const rules = normalizeValidationRules(type)
108
-
109
118
  // run validation for the current value
110
119
  const selfChecks = rules.map((rule) =>
111
- rule.validate(value, {
112
- ...restOfContext,
113
- parent,
114
- path,
115
- type,
116
- })
120
+ defer(() =>
121
+ rule.validate(value, {
122
+ ...restOfContext,
123
+ parent,
124
+ path,
125
+ type,
126
+ })
127
+ )
117
128
  )
118
129
 
119
130
  // run validation for nested values (conditionally)
120
- let nestedChecks: Array<Promise<ValidationMarker[]>> = []
131
+ let fieldChecks: Array<Observable<ValidationMarker[]>> = []
121
132
 
122
133
  const selfIsRequired = rules.some((rule) => rule.isRequired())
123
134
  const shouldRunNestedObjectValidation =
@@ -135,7 +146,7 @@ export async function validateItem({
135
146
  }, {})
136
147
 
137
148
  // Validation for rules set at the object level with `Rule.fields({/* ... */})`
138
- nestedChecks = nestedChecks.concat(
149
+ fieldChecks = fieldChecks.concat(
139
150
  rules
140
151
  .map((rule) => rule._fieldRules)
141
152
  .filter(isNonNullable)
@@ -144,18 +155,20 @@ export async function validateItem({
144
155
  const fieldType = fieldTypes[name]
145
156
  return normalizeValidationRules({...fieldType, validation}).map((subRule) => {
146
157
  const nestedValue = isRecord(value) ? value[name] : undefined
147
- return subRule.validate(nestedValue, {
148
- ...restOfContext,
149
- parent: value,
150
- path: path.concat(name),
151
- type: fieldType,
152
- })
158
+ return defer(() =>
159
+ subRule.validate(nestedValue, {
160
+ ...restOfContext,
161
+ parent: value,
162
+ path: path.concat(name),
163
+ type: fieldType,
164
+ })
165
+ )
153
166
  })
154
167
  })
155
168
  )
156
169
 
157
170
  // Validation from each field's schema `validation: Rule => {/* ... */}` function
158
- nestedChecks = nestedChecks.concat(
171
+ fieldChecks = fieldChecks.concat(
159
172
  type.fields.map((field) =>
160
173
  validateItem({
161
174
  ...restOfContext,
@@ -175,7 +188,7 @@ export async function validateItem({
175
188
  const shouldRunNestedValidationForArrays = type?.jsonType === 'array' && Array.isArray(value)
176
189
 
177
190
  if (shouldRunNestedValidationForArrays) {
178
- nestedChecks = nestedChecks.concat(
191
+ fieldChecks = fieldChecks.concat(
179
192
  value.map((item) =>
180
193
  validateItem({
181
194
  ...restOfContext,
@@ -205,7 +218,7 @@ export async function validateItem({
205
218
  new Map()
206
219
  )
207
220
 
208
- nestedChecks = nestedChecks.concat(
221
+ fieldChecks = fieldChecks.concat(
209
222
  value.markDefs.map((markDef) =>
210
223
  validateItem({
211
224
  ...restOfContext,
@@ -218,13 +231,33 @@ export async function validateItem({
218
231
  )
219
232
  }
220
233
 
221
- const results = (await Promise.all([...selfChecks, ...nestedChecks])).flat()
234
+ return defer(() => merge([...selfChecks, ...fieldChecks])).pipe(
235
+ mergeMap((validateNode) => concat(idle(), validateNode), 40),
236
+ mergeAll(),
237
+ toArray(),
238
+ filter((arr) => arr.length > 0),
239
+ map(flatten),
240
+ map((results) => {
241
+ // run `uniqBy` if `_fieldRules` are present because they can
242
+ // cause repeat markers
243
+ if (rules.some((rule) => rule._fieldRules)) {
244
+ return uniqBy(results, (rule) => JSON.stringify(rule))
245
+ }
246
+ return results
247
+ }),
248
+ filter((arr) => arr.length > 0)
249
+ )
250
+ }
222
251
 
223
- // run `uniqBy` if `_fieldRules` are present because they can
224
- // cause repeat markers
225
- if (rules.some((rule) => rule._fieldRules)) {
226
- return uniqBy(results, (rule) => JSON.stringify(rule))
227
- }
252
+ function idle(timeout?: number): Observable<never> {
253
+ return new Observable<never>((observer) => {
254
+ const handle = requestIdleCallback(
255
+ () => {
256
+ observer.complete()
257
+ },
258
+ timeout ? {timeout} : undefined
259
+ )
228
260
 
229
- return results
261
+ return () => cancelIdleCallback(handle)
262
+ })
230
263
  }