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
140 lines (117 loc) • 5.06 kB
text/typescript
import {type SanityDocument} from '@sanity/types'
import {useCallback, useEffect, useMemo, useRef} from 'react'
import {useObservable} from 'react-rx'
import {catchError, forkJoin, from, map, type Observable, of, switchMap} from 'rxjs'
import {useClient} from '../../../../../hooks/useClient'
import {getTransactionsLogs} from '../../../../../store/translog/getTransactionsLogs'
import {getPublishedId} from '../../../../../util/draftUtils'
import {RELEASES_STUDIO_CLIENT_OPTIONS} from '../../../../util/releasesClient'
import {type DocumentInRelease} from '../../../detail/useBundleDocuments'
export type RevertDocument = SanityDocument & {
_system?: {
delete: true
}
}
type RevertDocuments = RevertDocument[]
type DocumentRevertStates = RevertDocuments | null | undefined
export const useDocumentRevertStates = (releaseDocuments: DocumentInRelease[]) => {
const client = useClient(RELEASES_STUDIO_CLIENT_OPTIONS)
const observableClient = client.observable
const transactionId = releaseDocuments[0]?.document._rev
const {dataset} = client.config()
const resultPromiseRef = useRef<Promise<DocumentRevertStates> | null>(null)
const resolvedDocumentRevertStatesPromiseRef = useRef<
((value: DocumentRevertStates) => void) | null
>(null)
const resolvedDocumentRevertStatesResultRef = useRef<DocumentRevertStates | null>(null)
useEffect(() => {
if (!resultPromiseRef.current) {
const {promise, resolve} = Promise.withResolvers<DocumentRevertStates>()
resultPromiseRef.current = promise
resolvedDocumentRevertStatesPromiseRef.current = resolve
}
}, [])
const memoDocumentRevertStates = useMemo(() => {
if (!releaseDocuments.length) return of(undefined)
const publishedDocuments = releaseDocuments.map(({document}) => ({
...document,
_id: getPublishedId(document._id),
}))
const documentRevertStates$: Observable<RevertDocuments | null | undefined> = from(
getTransactionsLogs(
client,
publishedDocuments.map((document) => document._id),
{
toTransaction: transactionId,
// reverse order so most recent publish before release is second element
// (first is the release publish itself)
reverse: true,
},
),
).pipe(
map((transactions) => {
if (transactions.length === 0) throw new Error('No transactions found.')
const getDocumentTransaction = (docId: string) =>
// second element is the transaction before the release
transactions.filter(({documentIDs}) => documentIDs.includes(docId))[1]?.id
return publishedDocuments.map((document) => ({
docId: document._id,
revisionId: getDocumentTransaction(document._id),
}))
}),
switchMap((docRevisionPairs) => {
if (!docRevisionPairs) return of(undefined)
return forkJoin(
docRevisionPairs.map(({docId, revisionId}) => {
if (!revisionId) {
const {publishedDocumentExists, ...unpublishDocument} =
publishedDocuments.find((document) => document._id === docId) || {}
return of({
...unpublishDocument,
_system: {delete: true},
} as RevertDocument)
}
return observableClient
.request<{
documents: RevertDocuments
}>({
url: `/data/history/${dataset}/documents/${docId}?revision=${revisionId}`,
})
.pipe(
map(({documents: [revertDocument]}) => revertDocument),
catchError((err) => {
console.error(`Error fetching document ${docId}:`, err)
return of(undefined)
}),
)
}),
)
}),
map((results) => results?.filter((result) => result !== undefined)),
catchError((err) => {
console.error('Error in document revert states pipeline:', err)
return of(undefined)
}),
)
return documentRevertStates$
}, [client, releaseDocuments, transactionId, observableClient, dataset])
const documentRevertStatesResult = useObservable(memoDocumentRevertStates, null)
useEffect(() => {
if (documentRevertStatesResult !== null) {
resolvedDocumentRevertStatesResultRef.current = documentRevertStatesResult
// Resolve promise if it exists
if (resolvedDocumentRevertStatesPromiseRef.current) {
resolvedDocumentRevertStatesPromiseRef.current(documentRevertStatesResult)
resolvedDocumentRevertStatesPromiseRef.current = null
resultPromiseRef.current = null // Reset resultPromiseRef for future fetches
}
}
}, [documentRevertStatesResult])
return useCallback(() => {
if (resolvedDocumentRevertStatesResultRef.current) {
// Return resolved value immediately if available
return Promise.resolve(resolvedDocumentRevertStatesResultRef.current)
}
return resultPromiseRef.current
}, [])
}