-
Notifications
You must be signed in to change notification settings - Fork 427
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(preview): add
unstable_observeDocument(s)
to preview store (#7176
) * chore: remove futile circular dependency workaround * refactor(core): lift global listener out of preview store * fix(preview): cleanup code so it matches current conventions + add some docs * feat(preview): add unstable_observeDocument + unstable_observeDocuments to preview store * fix: use includeMutations: false * fix: expect ts error on includeMutations: false for now
- Loading branch information
Showing
9 changed files
with
240 additions
and
85 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import {type MutationEvent, type SanityClient, type WelcomeEvent} from '@sanity/client' | ||
import {defer, merge, timer} from 'rxjs' | ||
import {filter, share, shareReplay} from 'rxjs/operators' | ||
|
||
/** | ||
* @internal | ||
* Creates a listener that will emit 'welcome' for all new subscribers immediately, and thereafter emit at every mutation event | ||
*/ | ||
export function createGlobalListener(client: SanityClient) { | ||
const allEvents$ = defer(() => | ||
client.listen( | ||
'*[!(_id in path("_.**"))]', | ||
{}, | ||
{ | ||
events: ['welcome', 'mutation'], | ||
includeResult: false, | ||
includePreviousRevision: false, | ||
// @ts-expect-error - will be enabled by https://github.com/sanity-io/client/pull/872 | ||
includeMutations: false, | ||
visibility: 'query', | ||
effectFormat: 'mendoza', | ||
tag: 'preview.global', | ||
}, | ||
), | ||
).pipe( | ||
filter( | ||
(event): event is WelcomeEvent | MutationEvent => | ||
event.type === 'welcome' || event.type === 'mutation', | ||
), | ||
share({resetOnRefCountZero: () => timer(2000), resetOnComplete: true}), | ||
) | ||
|
||
const welcome$ = allEvents$.pipe( | ||
filter((event) => event.type === 'welcome'), | ||
shareReplay({refCount: true, bufferSize: 1}), | ||
) | ||
const mutations$ = allEvents$.pipe(filter((event) => event.type === 'mutation')).pipe(share()) | ||
return merge(welcome$, mutations$) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import {type MutationEvent, type SanityClient, type WelcomeEvent} from '@sanity/client' | ||
import {type SanityDocument} from '@sanity/types' | ||
import {memoize, uniq} from 'lodash' | ||
import {EMPTY, finalize, type Observable, of} from 'rxjs' | ||
import {concatMap, map, scan, shareReplay} from 'rxjs/operators' | ||
|
||
import {type ApiConfig} from './types' | ||
import {applyMendozaPatch} from './utils/applyMendozaPatch' | ||
import {debounceCollect} from './utils/debounceCollect' | ||
|
||
export function createObserveDocument({ | ||
mutationChannel, | ||
client, | ||
}: { | ||
client: SanityClient | ||
mutationChannel: Observable<WelcomeEvent | MutationEvent> | ||
}) { | ||
const getBatchFetcher = memoize( | ||
function getBatchFetcher(apiConfig: {dataset: string; projectId: string}) { | ||
const _client = client.withConfig(apiConfig) | ||
|
||
function batchFetchDocuments(ids: [string][]) { | ||
return _client.observable | ||
.fetch(`*[_id in $ids]`, {ids: uniq(ids.flat())}, {tag: 'preview.observe-document'}) | ||
.pipe( | ||
// eslint-disable-next-line max-nested-callbacks | ||
map((result) => ids.map(([id]) => result.find((r: {_id: string}) => r._id === id))), | ||
) | ||
} | ||
return debounceCollect(batchFetchDocuments, 100) | ||
}, | ||
(apiConfig) => apiConfig.dataset + apiConfig.projectId, | ||
) | ||
|
||
const MEMO: Record<string, Observable<SanityDocument | undefined>> = {} | ||
|
||
function observeDocument(id: string, apiConfig?: ApiConfig) { | ||
const _apiConfig = apiConfig || { | ||
dataset: client.config().dataset!, | ||
projectId: client.config().projectId!, | ||
} | ||
const fetchDocument = getBatchFetcher(_apiConfig) | ||
return mutationChannel.pipe( | ||
concatMap((event) => { | ||
if (event.type === 'welcome') { | ||
return fetchDocument(id).pipe(map((document) => ({type: 'sync' as const, document}))) | ||
} | ||
return event.documentId === id ? of(event) : EMPTY | ||
}), | ||
scan((current: SanityDocument | undefined, event) => { | ||
if (event.type === 'sync') { | ||
return event.document | ||
} | ||
if (event.type === 'mutation') { | ||
return applyMutationEvent(current, event) | ||
} | ||
//@ts-expect-error - this should never happen | ||
throw new Error(`Unexpected event type: "${event.type}"`) | ||
}, undefined), | ||
) | ||
} | ||
return function memoizedObserveDocument(id: string, apiConfig?: ApiConfig) { | ||
const key = apiConfig ? `${id}-${JSON.stringify(apiConfig)}` : id | ||
if (!(key in MEMO)) { | ||
MEMO[key] = observeDocument(id, apiConfig).pipe( | ||
finalize(() => delete MEMO[key]), | ||
shareReplay({bufferSize: 1, refCount: true}), | ||
) | ||
} | ||
return MEMO[key] | ||
} | ||
} | ||
|
||
function applyMutationEvent(current: SanityDocument | undefined, event: MutationEvent) { | ||
if (event.previousRev !== current?._rev) { | ||
console.warn('Document out of sync, skipping mutation') | ||
return current | ||
} | ||
if (!event.effects) { | ||
throw new Error( | ||
'Mutation event is missing effects. Is the listener set up with effectFormat=mendoza?', | ||
) | ||
} | ||
const next = applyMendozaPatch(current, event.effects.apply) | ||
return {...next, _rev: event.resultRev} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.