Skip to content

Commit

Permalink
fix(preview): allow null as valid cache/memo value for preview fiel…
Browse files Browse the repository at this point in the history
…ds (#7551)

* refactor(preview): explicitly type the interface required by observeFields

* test(preview): add a test for observePreview memo

* fix(sanity): allow "null" as valid cache/memo value for preview fields
  • Loading branch information
bjoerge authored Sep 26, 2024
1 parent 7aed0c0 commit 7b9b556
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 6 deletions.
47 changes: 47 additions & 0 deletions packages/sanity/src/core/preview/__test__/observeFields.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {describe, expect, it} from '@jest/globals'
import {firstValueFrom, of, Subject} from 'rxjs'
import {take, tap} from 'rxjs/operators'

import {type ClientLike, createObserveFields} from '../observeFields'
import {type InvalidationChannelEvent} from '../types'

describe('observeFields', () => {
it('should cache the last known value and emit sync', async () => {
const client: ClientLike = {
observable: {
fetch: (query) => {
expect(query).toEqual('[*[_id in ["foo"]][0...1]{_id,_rev,_type,bar}][0...1]')
return of([
[
// no result
],
])
},
},
withConfig: () => client,
}

const invalidationChannel = new Subject<InvalidationChannelEvent>()
const observeFields = createObserveFields({
invalidationChannel,
client,
})
const first = firstValueFrom(observeFields('foo', ['bar']).pipe(take(1)))
invalidationChannel.next({type: 'connected'})

expect(await first).toMatchInlineSnapshot(`null`)

// After we got first value from server and it turned out to be `null`, we should have `null` as the memoized sync value
let syncValue = undefined
observeFields('foo', ['bar'])
.pipe(
tap((value) => {
syncValue = value
}),
take(1),
)
.subscribe()
.unsubscribe()
expect(syncValue).toBe(null)
})
})
27 changes: 21 additions & 6 deletions packages/sanity/src/core/preview/observeFields.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {type SanityClient} from '@sanity/client'
import {difference, flatten, memoize} from 'lodash'
import {
combineLatest,
Expand Down Expand Up @@ -46,14 +45,29 @@ type Cache = {
[id: string]: CachedFieldObserver[]
}

/**
* Note: this should be the minimal interface createObserveFields needs to function
* It should be kept compatible with the Sanity Client
*/
export interface ClientLike {
withConfig(config: ApiConfig): ClientLike
observable: {
fetch: (
query: string,
params: Record<string, string>,
options: {tag: string},
) => Observable<unknown>
}
}

/**
* Creates a function that allows observing individual fields on a document.
* It will automatically debounce and batch requests, and maintain an in-memory cache of the latest field values
* @param options - Options to use when creating the observer
* @internal
*/
export function createObserveFields(options: {
client: SanityClient
client: ClientLike
invalidationChannel: Observable<InvalidationChannelEvent>
}) {
const {client: currentDatasetClient, invalidationChannel} = options
Expand All @@ -63,11 +77,11 @@ export function createObserveFields(options: {
)
}

function fetchAllDocumentPathsWith(client: SanityClient) {
function fetchAllDocumentPathsWith(client: ClientLike) {
return function fetchAllDocumentPath(selections: Selection[]) {
const combinedSelections = combineSelections(selections)
return client.observable
.fetch(toQuery(combinedSelections), {}, {tag: 'preview.document-paths'} as any)
.fetch(toQuery(combinedSelections), {}, {tag: 'preview.document-paths'})
.pipe(map((result: any) => reassemble(result, combinedSelections)))
}
}
Expand Down Expand Up @@ -140,9 +154,10 @@ export function createObserveFields(options: {
fields: FieldName[],
apiConfig?: ApiConfig,
): CachedFieldObserver {
let latest: T | null = null
// Note: `undefined` means the memo has not been set, while `null` means the memo is explicitly set to null (e.g. we did fetch, but got null back)
let latest: T | undefined | null = undefined
const changes$ = merge(
defer(() => (latest === null ? EMPTY : observableOf(latest))),
defer(() => (latest === undefined ? EMPTY : observableOf(latest))),
(apiConfig
? (crossDatasetListenFields(id, fields, apiConfig) as any)
: currentDatasetListenFields(id, fields)) as Observable<T>,
Expand Down

0 comments on commit 7b9b556

Please sign in to comment.