UNPKG

sanity

Version:

Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches

118 lines (110 loc) 3.81 kB
import {type QueryParams, type SanityClient} from '@sanity/client' import {sortedIndex} from 'lodash' import {of} from 'rxjs' import {distinctUntilChanged, filter, map, mergeMap, scan, tap} from 'rxjs/operators' import {type SourceClientOptions} from '../config/types' import {versionedClient} from '../studioClient' export type DocumentIdSetObserverState = { status: 'reconnecting' | 'connected' documentIds: string[] } interface LiveDocumentIdSetOptions { insert?: 'sorted' | 'prepend' | 'append' apiVersion?: SourceClientOptions['apiVersion'] } export function createDocumentIdSetObserver(client: SanityClient) { return function observe( queryFilter: string, params?: QueryParams, options: LiveDocumentIdSetOptions = {}, ) { const {insert: insertOption = 'sorted', apiVersion} = options const query = `*[${queryFilter}]._id` function fetchFilter() { return versionedClient(client, apiVersion) .observable.fetch(query, params, { tag: 'preview.observe-document-set.fetch', }) .pipe( tap((result) => { if (!Array.isArray(result)) { throw new Error( `Expected query to return array of documents, but got ${typeof result}`, ) } }), ) } return versionedClient(client, apiVersion) .observable.listen(`*[${queryFilter}]`, params, { visibility: 'transaction', events: ['welcome', 'mutation', 'reconnect'], includeResult: false, includeMutations: false, includeAllVersions: true, tag: 'preview.observe-document-set.listen', }) .pipe( mergeMap((event) => { return event.type === 'welcome' ? fetchFilter().pipe(map((result) => ({type: 'fetch' as const, result}))) : of(event) }), scan( ( state: DocumentIdSetObserverState | undefined, event, ): DocumentIdSetObserverState | undefined => { if (event.type === 'reconnect') { return { documentIds: state?.documentIds || [], ...state, status: 'reconnecting' as const, } } if (event.type === 'fetch') { return {...state, status: 'connected' as const, documentIds: event.result} } if (event.type === 'mutation') { if (event.transition === 'update') { // ignore updates, as we're only interested in documents appearing and disappearing from the set return state } if (event.transition === 'appear') { return { status: 'connected', documentIds: insert(state?.documentIds || [], event.documentId, insertOption), } } if (event.transition === 'disappear') { return { status: 'connected', documentIds: state?.documentIds ? state.documentIds.filter((id) => id !== event.documentId) : [], } } } return state }, undefined, ), distinctUntilChanged(), filter( (state: DocumentIdSetObserverState | undefined): state is DocumentIdSetObserverState => state !== undefined, ), ) } } function insert<T>(array: T[], element: T, strategy: 'sorted' | 'prepend' | 'append') { let index if (strategy === 'prepend') { index = 0 } else if (strategy === 'append') { index = array.length } else { index = sortedIndex(array, element) } return array.toSpliced(index, 0, element) }