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

101 lines (86 loc) 3.37 kB
import {from, isObservable, type Observable, of as observableOf} from 'rxjs' import {publishReplay, refCount, switchMap} from 'rxjs/operators' import {isRecord} from 'sanity' import {type PaneNode, type RouterPaneSiblingContext, type UnresolvedPaneNode} from '../types' import {PaneResolutionError} from './PaneResolutionError' interface Serializable { serialize: (...args: never[]) => unknown } const isPromise = (thing: any): thing is PromiseLike<unknown> => { return !!thing && typeof thing?.then === 'function' } const isSerializable = (thing: unknown): thing is Serializable => { if (!isRecord(thing)) return false return typeof thing.serialize === 'function' } /** * The signature of the function used to take an `UnresolvedPaneNode` and turn * it into an `Observable<PaneNode>`. */ export type PaneResolver = ( unresolvedPane: UnresolvedPaneNode | undefined, context: RouterPaneSiblingContext, flatIndex: number, ) => Observable<PaneNode> export type PaneResolverMiddleware = (paneResolveFn: PaneResolver) => PaneResolver const rethrowWithPaneResolutionErrors: PaneResolverMiddleware = (next) => (unresolvedPane, context, flatIndex) => { try { return next(unresolvedPane, context, flatIndex) } catch (e) { // re-throw errors that are already `PaneResolutionError`s if (e instanceof PaneResolutionError) { throw e } // anything else, wrap with `PaneResolutionError` and set the underlying // error as a the `cause` throw new PaneResolutionError({ message: typeof e?.message === 'string' ? e.message : '', context, cause: e, }) } } const wrapWithPublishReplay: PaneResolverMiddleware = (next) => (...args) => { return next(...args).pipe( // need to add publishReplay + refCount to ensure new subscribers always // get an emission. without this, memoized observables may get stuck // waiting for their first emissions resulting in a loading pane publishReplay(1), refCount(), ) } export function createPaneResolver(middleware: PaneResolverMiddleware): PaneResolver { // note: this API includes a middleware/wrapper function because the function // is recursive. we want to call the wrapped version of the function always // (even inside of nested calls) so the identifier invoked for the recursion // should be the wrapped version const resolvePane = rethrowWithPaneResolutionErrors( wrapWithPublishReplay( middleware((unresolvedPane, context, flatIndex) => { if (!unresolvedPane) { throw new PaneResolutionError({ message: 'Pane returned no child', context, helpId: 'structure-item-returned-no-child', }) } if (isPromise(unresolvedPane) || isObservable(unresolvedPane)) { return from(unresolvedPane).pipe( switchMap((result) => resolvePane(result, context, flatIndex)), ) } if (isSerializable(unresolvedPane)) { return resolvePane(unresolvedPane.serialize(context), context, flatIndex) } if (typeof unresolvedPane === 'function') { return resolvePane(unresolvedPane(context.id, context), context, flatIndex) } return observableOf(unresolvedPane) }), ), ) return resolvePane }