1
1
import { type SanityClient } from '@sanity/client'
2
- import { isReference , type Schema , type ValidationMarker } from '@sanity/types'
3
- import { reduce as reduceJSON } from 'json-reduce'
2
+ import { type Schema } from '@sanity/types'
4
3
import { omit } from 'lodash'
5
- import {
6
- asyncScheduler ,
7
- combineLatest ,
8
- concat ,
9
- defer ,
10
- from ,
11
- lastValueFrom ,
12
- type Observable ,
13
- of ,
14
- timer ,
15
- } from 'rxjs'
16
- import {
17
- distinct ,
18
- distinctUntilChanged ,
19
- first ,
20
- groupBy ,
21
- map ,
22
- mergeMap ,
23
- scan ,
24
- shareReplay ,
25
- skip ,
26
- throttleTime ,
27
- } from 'rxjs/operators'
28
- import { exhaustMapWithTrailing } from 'rxjs-exhaustmap-with-trailing'
4
+ import { asyncScheduler , type Observable } from 'rxjs'
5
+ import { distinctUntilChanged , map , shareReplay , throttleTime } from 'rxjs/operators'
29
6
import shallowEquals from 'shallow-equals'
30
7
31
8
import { type SourceClientOptions } from '../../../../config'
32
9
import { type LocaleSource } from '../../../../i18n'
33
10
import { type DraftsModelDocumentAvailability } from '../../../../preview'
34
- import { validateDocumentObservable , type ValidationContext } from '../../../../validation'
11
+ import { validateDocumentWithReferences , type ValidationStatus } from '../../../../validation'
35
12
import { type IdPair } from '../types'
36
13
import { memoize } from '../utils/createMemoizer'
37
14
import { editState } from './editState'
38
15
import { memoizeKeyGen } from './memoizeKeyGen'
39
16
40
- /**
41
- * @hidden
42
- * @beta */
43
- export interface ValidationStatus {
44
- isValidating : boolean
45
- validation : ValidationMarker [ ]
46
- revision ?: string
47
- }
48
-
49
- const INITIAL_VALIDATION_STATUS : ValidationStatus = {
50
- isValidating : true ,
51
- validation : [ ] ,
52
- }
53
-
54
- function findReferenceIds ( obj : any ) : Set < string > {
55
- return reduceJSON (
56
- obj ,
57
- ( acc , node ) => {
58
- if ( isReference ( node ) ) {
59
- acc . add ( node . _ref )
60
- }
61
- return acc
62
- } ,
63
- new Set < string > ( ) ,
64
- )
65
- }
66
-
67
- const EMPTY_VALIDATION : ValidationMarker [ ] = [ ]
68
-
69
- type GetDocumentExists = NonNullable < ValidationContext [ 'getDocumentExists' ] >
70
-
71
- type ObserveDocumentPairAvailability = ( id : string ) => Observable < DraftsModelDocumentAvailability >
72
-
73
- const listenDocumentExists = (
74
- observeDocumentAvailability : ObserveDocumentPairAvailability ,
75
- id : string ,
76
- ) : Observable < boolean > =>
77
- observeDocumentAvailability ( id ) . pipe ( map ( ( { published} ) => published . available ) )
78
-
79
17
// throttle delay for document updates (i.e. time between responding to changes in the current document)
80
18
const DOC_UPDATE_DELAY = 200
81
19
82
- // throttle delay for referenced document updates (i.e. time between responding to changes in referenced documents)
83
- const REF_UPDATE_DELAY = 1000
84
-
85
20
function shareLatestWithRefCount < T > ( ) {
86
21
return shareReplay < T > ( { bufferSize : 1 , refCount : true } )
87
22
}
@@ -92,7 +27,7 @@ export const validation = memoize(
92
27
ctx : {
93
28
client : SanityClient
94
29
getClient : ( options : SourceClientOptions ) => SanityClient
95
- observeDocumentPairAvailability : ObserveDocumentPairAvailability
30
+ observeDocumentPairAvailability : ( id : string ) => Observable < DraftsModelDocumentAvailability >
96
31
schema : Schema
97
32
i18n : LocaleSource
98
33
serverActionsEnabled : Observable < boolean >
@@ -114,81 +49,7 @@ export const validation = memoize(
114
49
shareLatestWithRefCount ( ) ,
115
50
)
116
51
117
- const referenceIds$ = document$ . pipe (
118
- map ( ( document ) => findReferenceIds ( document ) ) ,
119
- mergeMap ( ( ids ) => from ( ids ) ) ,
120
- )
121
-
122
- // Note: we only use this to trigger a re-run of validation when a referenced document is published/unpublished
123
- const referenceExistence$ = referenceIds$ . pipe (
124
- groupBy ( ( id ) => id , { duration : ( ) => timer ( 1000 * 60 * 30 ) } ) ,
125
- mergeMap ( ( id$ ) =>
126
- id$ . pipe (
127
- distinct ( ) ,
128
- mergeMap ( ( id ) =>
129
- listenDocumentExists ( ctx . observeDocumentPairAvailability , id ) . pipe (
130
- map (
131
- // eslint-disable-next-line max-nested-callbacks
132
- ( result ) => [ id , result ] as const ,
133
- ) ,
134
- ) ,
135
- ) ,
136
- ) ,
137
- ) ,
138
- scan ( ( acc : Record < string , boolean > , [ id , result ] ) : Record < string , boolean > => {
139
- if ( acc [ id ] === result ) {
140
- return acc
141
- }
142
- return { ...acc , [ id ] : result }
143
- } , { } ) ,
144
- distinctUntilChanged ( shallowEquals ) ,
145
- shareLatestWithRefCount ( ) ,
146
- )
147
-
148
- // Provided to individual validation functions to support using existence of a weakly referenced document
149
- // as part of the validation rule (used by references in place)
150
- const getDocumentExists : GetDocumentExists = ( { id} ) =>
151
- lastValueFrom (
152
- referenceExistence$ . pipe (
153
- // If the id is not present as key in the `referenceExistence` map it means it's existence status
154
- // isn't yet loaded, so we want to wait until it is
155
- first ( ( referenceExistence ) => id in referenceExistence ) ,
156
- map ( ( referenceExistence ) => referenceExistence [ id ] ) ,
157
- ) ,
158
- )
159
-
160
- const referenceDocumentUpdates$ = referenceExistence$ . pipe (
161
- // we'll skip the first emission since the document already gets an initial validation pass
162
- // we're only interested in updates in referenced documents after that
163
- skip ( 1 ) ,
164
- throttleTime ( REF_UPDATE_DELAY , asyncScheduler , { leading : true , trailing : true } ) ,
165
- )
166
-
167
- return combineLatest ( [ document$ , concat ( of ( null ) , referenceDocumentUpdates$ ) ] ) . pipe (
168
- map ( ( [ document ] ) => document ) ,
169
- exhaustMapWithTrailing ( ( document ) => {
170
- return defer ( ( ) => {
171
- if ( ! document ?. _type ) {
172
- return of ( { validation : EMPTY_VALIDATION , isValidating : false } )
173
- }
174
- return concat (
175
- of ( { isValidating : true , revision : document . _rev } ) ,
176
- validateDocumentObservable ( {
177
- document,
178
- getClient : ctx . getClient ,
179
- getDocumentExists,
180
- i18n : ctx . i18n ,
181
- schema : ctx . schema ,
182
- environment : 'studio' ,
183
- } ) . pipe (
184
- map ( ( validationMarkers ) => ( { validation : validationMarkers , isValidating : false } ) ) ,
185
- ) ,
186
- )
187
- } )
188
- } ) ,
189
- scan ( ( acc , next ) => ( { ...acc , ...next } ) , INITIAL_VALIDATION_STATUS ) ,
190
- shareLatestWithRefCount ( ) ,
191
- )
52
+ return validateDocumentWithReferences ( ctx , document$ )
192
53
} ,
193
54
( ctx , idPair , typeName ) => {
194
55
return memoizeKeyGen ( ctx . client , idPair , typeName )
0 commit comments