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

146 lines (137 loc) 4.87 kB
import {type SanityClient} from '@sanity/client' import {BehaviorSubject, from, of} from 'rxjs' import {catchError, map, scan, shareReplay, startWith, switchMap, tap} from 'rxjs/operators' import {getDocumentVariantType} from '../../util/getDocumentVariantType' import {getDocumentTransactions} from './getDocumentTransactions' import {getEditEvents} from './getEditEvents' import {type DocumentGroupEvent, isCreateDocumentVersionEvent} from './types' import {addEventId, removeDupes} from './utils' export interface EventsObservableValue { events: DocumentGroupEvent[] nextCursor: string loading: boolean error: null | Error } const INITIAL_VALUE: EventsObservableValue = { events: [], nextCursor: '', loading: true, error: null, } interface InitialFetchEventsOptions { client: SanityClient documentId: string } export function getInitialFetchEvents({client, documentId}: InitialFetchEventsOptions) { const documentVariantType = getDocumentVariantType(documentId) const refetchEventsTrigger$ = new BehaviorSubject<{ cursor: string | null origin: 'loadMore' | 'reload' | 'initial' }>({ cursor: null, origin: 'initial', }) const fetchEvents = ({limit, nextCursor}: {limit: number; nextCursor: string | null}) => { const params = new URLSearchParams({ limit: limit.toString(), }) if (nextCursor) { params.append('nextCursor', nextCursor) } return client.observable .request<{ events: Record<string, Omit<DocumentGroupEvent, 'id'>[]> nextCursor: string }>({ url: `/data/history/${client.config().dataset}/events/documents/${documentId}?${params.toString()}`, tag: 'get-document-events', }) .pipe( map((response) => { return { events: response.events[documentId]?.map((ev) => addEventId(ev, documentVariantType)) || [], nextCursor: response.nextCursor, loading: false, error: null, } }), ) } const fetchTransactions = (events: DocumentGroupEvent[]) => { const eventWithRevision = documentVariantType === 'version' ? events.find(isCreateDocumentVersionEvent) : events.find((event) => 'versionRevisionId' in event && event.versionRevisionId) const revisionId = eventWithRevision && 'versionRevisionId' in eventWithRevision && eventWithRevision.versionRevisionId if (!revisionId) { return of([]) } return from( getDocumentTransactions({ client, documentId, fromTransaction: revisionId, toTransaction: undefined, // We need to get up to the present moment }), ) } let nextCursor: string = '' return { events$: refetchEventsTrigger$.pipe( switchMap(({cursor, origin}) => { return fetchEvents({ nextCursor: cursor, limit: origin === 'reload' ? 10 : 100, }).pipe( switchMap((response) => { if (documentVariantType === 'published' || origin === 'loadMore') { // For the published document we don't need to fetch the edit transactions. return of({...response, origin}) } return fetchTransactions(response.events).pipe( map((transactions) => { const editEvents = getEditEvents(transactions, documentId, false) return {...response, events: [...editEvents, ...response.events], origin} }), ) }), catchError((error: Error) => { console.error('Error fetching events', error) return [{events: [], nextCursor: '', loading: false, error: error, origin}] }), startWith({events: [], nextCursor: '', loading: true, error: null, origin}), ) }), scan((prev, next) => { return { events: removeDupes(prev.events, next.events), // If we are reloading, we should keep the cursor as it was before. nextCursor: next.origin === 'reload' ? prev.nextCursor : next.nextCursor, loading: next.loading, error: next.error, } }, INITIAL_VALUE), tap((response) => { nextCursor = response.nextCursor }), shareReplay(1), ), /** * Loads new events for the document, fetching the latest events from the API. */ reloadEvents: () => refetchEventsTrigger$.next({cursor: null, origin: 'reload'}), /** * Loads more events for the document, fetching the next batch of events from the API. */ loadMore: () => { const lastCursorUsed = refetchEventsTrigger$.getValue().cursor if (nextCursor && lastCursorUsed !== nextCursor) { refetchEventsTrigger$.next({origin: 'loadMore', cursor: nextCursor}) } }, } }