Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/core/src/_exports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ export {
export {
type DatasetHandle,
type DocumentHandle,
type DocumentSource,
type DocumentTypeHandle,
type PerspectiveHandle,
type ProjectHandle,
type ReleasePerspective,
type SanityConfig,
sourceFor,
} from '../config/sanityConfig'
export {getDatasetsState, resolveDatasets} from '../datasets/datasets'
export {
Expand Down
29 changes: 29 additions & 0 deletions packages/core/src/config/sanityConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,32 @@ export interface SanityConfig extends DatasetHandle, PerspectiveHandle {
enabled: boolean
}
}

/**
* Represents a source which can be used by various functionality.
*
* @see sourceFor For how to initialize a new source for a dataset.
* @public
*/
export type DocumentSource = {
[__sourceData]: {
projectId: string
dataset: string
}
}

/**
* An internal symbol to avoid users to access data inside here.
*
* @internal
*/
export const __sourceData = Symbol('Sanity.DocumentSource')

/**
* Creates a new {@link DocumentSource} object based on a projectId and dataset.
*
* @public
*/
export function sourceFor(data: {projectId: string; dataset: string}): DocumentSource {
return {[__sourceData]: data}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
tap,
} from 'rxjs'

import {sourceFor} from '../config/sanityConfig'
import {getQueryState, resolveQuery} from '../query/queryStore'
import {type BoundDatasetKey} from '../store/createActionBinder'
import {type StoreContext} from '../store/defineStore'
Expand Down Expand Up @@ -66,8 +67,7 @@ export const subscribeToStateAndFetchBatches = ({
params,
tag: PREVIEW_TAG,
perspective: PREVIEW_PERSPECTIVE,
projectId,
dataset,
source: sourceFor({projectId, dataset}),
})
const source$ = defer(() => {
if (getCurrent() === undefined) {
Expand All @@ -78,8 +78,7 @@ export const subscribeToStateAndFetchBatches = ({
tag: PREVIEW_TAG,
perspective: PREVIEW_PERSPECTIVE,
signal: controller.signal,
projectId,
dataset,
source: sourceFor({projectId, dataset}),
}),
).pipe(switchMap(() => observable))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
tap,
} from 'rxjs'

import {sourceFor} from '../config/sanityConfig'
import {getQueryState, resolveQuery} from '../query/queryStore'
import {type BoundDatasetKey} from '../store/createActionBinder'
import {type StoreContext} from '../store/defineStore'
Expand Down Expand Up @@ -96,8 +97,7 @@
const {getCurrent, observable} = getQueryState<ProjectionQueryResult[]>(instance, {
query,
params,
projectId,
dataset,
source: sourceFor({projectId, dataset}),
tag: PROJECTION_TAG,
perspective: PROJECTION_PERSPECTIVE,
})
Expand All @@ -108,8 +108,7 @@
resolveQuery<ProjectionQueryResult[]>(instance, {
query,
params,
projectId,
dataset,
source: sourceFor({projectId, dataset}),
tag: PROJECTION_TAG,
perspective: PROJECTION_PERSPECTIVE,
signal: controller.signal,
Expand Down Expand Up @@ -156,7 +155,7 @@
error: (err) => {
// eslint-disable-next-line no-console
console.error('Error fetching projection batches:', err)
// TODO: Potentially update state to reflect error state for affected projections?

Check warning on line 158 in packages/core/src/projection/subscribeToStateAndFetchBatches.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected 'todo' comment: 'TODO: Potentially update state to...'
},
})

Expand Down
62 changes: 34 additions & 28 deletions packages/core/src/query/queryStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {delay, filter, firstValueFrom, Observable, of, Subject} from 'rxjs'
import {beforeEach, describe, expect, it, vi} from 'vitest'

import {getClientState} from '../client/clientStore'
import {sourceFor as getSource} from '../config/sanityConfig'
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
import {type StateSource} from '../store/createStateSourceAction'
import {getQueryState, resolveQuery} from './queryStore'
Expand All @@ -17,6 +18,7 @@ vi.mock('../client/clientStore', () => ({
}))

describe('queryStore', () => {
const source = getSource({projectId: 'test', dataset: 'test'})
let instance: SanityInstance
let liveEvents: Subject<LiveEvent>
let fetch: SanityClient['observable']['fetch']
Expand Down Expand Up @@ -61,7 +63,7 @@ describe('queryStore', () => {

it('initializes query state and cleans up after unsubscribe', async () => {
const query = '*[_type == "movie"]'
const state = getQueryState(instance, {query})
const state = getQueryState(instance, {query, source, perspective: 'drafts'})

// Initially undefined before subscription
expect(state.getCurrent()).toBeUndefined()
Expand Down Expand Up @@ -90,7 +92,7 @@ describe('queryStore', () => {

it('maintains state when multiple subscribers exist', async () => {
const query = '*[_type == "movie"]'
const state = getQueryState(instance, {query})
const state = getQueryState(instance, {query, source, perspective: 'drafts'})

// Add two subscribers
const unsubscribe1 = state.subscribe()
Expand Down Expand Up @@ -127,13 +129,13 @@ describe('queryStore', () => {
it('resolveQuery works without affecting subscriber cleanup', async () => {
const query = '*[_type == "movie"]'

const state = getQueryState(instance, {query})
const state = getQueryState(instance, {query, source, perspective: 'drafts'})

// Check that getQueryState starts undefined
expect(state.getCurrent()).toBeUndefined()

// Use resolveQuery which should not add a subscriber
const result = await resolveQuery(instance, {query})
const result = await resolveQuery(instance, {query, source, perspective: 'drafts'})
expect(result).toEqual([
{_id: 'movie1', _type: 'movie', title: 'Movie 1'},
{_id: 'movie2', _type: 'movie', title: 'Movie 2'},
Expand All @@ -160,7 +162,12 @@ describe('queryStore', () => {
const abortController = new AbortController()

// Create a promise that will reject when aborted
const queryPromise = resolveQuery(instance, {query, signal: abortController.signal})
const queryPromise = resolveQuery(instance, {
query,
source,
perspective: 'drafts',
signal: abortController.signal,
})

// Abort the request
abortController.abort()
Expand All @@ -169,7 +176,9 @@ describe('queryStore', () => {
await expect(queryPromise).rejects.toThrow('The operation was aborted.')

// Verify state is cleared after abort
expect(getQueryState(instance, {query}).getCurrent()).toBeUndefined()
expect(
getQueryState(instance, {query, source, perspective: 'drafts'}).getCurrent(),
).toBeUndefined()
})

it('refetches query when receiving live event with matching sync tag', async () => {
Expand All @@ -188,7 +197,11 @@ describe('queryStore', () => {
)

const query = '*[_type == "movie"]'
const state = getQueryState<{_id: string; _type: string; title: string}[]>(instance, {query})
const state = getQueryState<{_id: string; _type: string; title: string}[]>(instance, {
query,
source,
perspective: 'drafts',
})

const unsubscribe = state.subscribe()
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
Expand Down Expand Up @@ -219,7 +232,7 @@ describe('queryStore', () => {
)

const query = '*[_type == "movie"]'
const state = getQueryState(instance, {query})
const state = getQueryState(instance, {query, source, perspective: 'drafts'})

const unsubscribe = state.subscribe()
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
Expand Down Expand Up @@ -252,7 +265,7 @@ describe('queryStore', () => {
)

const query = '*[_type == "movie"]'
const state = getQueryState(instance, {query})
const state = getQueryState(instance, {query, source, perspective: 'drafts'})

const unsubscribe = state.subscribe()
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
Expand Down Expand Up @@ -289,7 +302,7 @@ describe('queryStore', () => {
)

const query = '*[_type == "movie"]'
const state = getQueryState(instance, {query})
const state = getQueryState(instance, {query, source, perspective: 'drafts'})
const unsubscribe = state.subscribe()

// Verify error is thrown when accessing state
Expand All @@ -300,7 +313,7 @@ describe('queryStore', () => {

it('delays query state removal after unsubscribe', async () => {
const query = '*[_type == "movie"]'
const state = getQueryState(instance, {query})
const state = getQueryState(instance, {query, source, perspective: 'drafts'})
const unsubscribe = state.subscribe()

await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
Expand All @@ -316,7 +329,7 @@ describe('queryStore', () => {

it('preserves query state if a new subscriber subscribes before cleanup delay', async () => {
const query = '*[_type == "movie"]'
const state = getQueryState(instance, {query})
const state = getQueryState(instance, {query, source, perspective: 'drafts'})
const unsubscribe1 = state.subscribe()

await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
Expand Down Expand Up @@ -352,22 +365,16 @@ describe('queryStore', () => {
SanityClient['observable']['fetch']
>
}) as SanityClient['observable']['fetch'])

const draftsInstance = createSanityInstance({
projectId: 'test',
dataset: 'test',
// Same query/options, different implicit perspectives via instance.config
const sDrafts = getQueryState<{_id: string}[]>(instance, {
query: '*[_type == "movie"]',
source,
perspective: 'drafts',
})
const publishedInstance = createSanityInstance({
projectId: 'test',
dataset: 'test',
perspective: 'published',
})

// Same query/options, different implicit perspectives via instance.config
const sDrafts = getQueryState<{_id: string}[]>(draftsInstance, {query: '*[_type == "movie"]'})
const sPublished = getQueryState<{_id: string}[]>(publishedInstance, {
const sPublished = getQueryState<{_id: string}[]>(instance, {
query: '*[_type == "movie"]',
source,
perspective: 'published',
})

const unsubDrafts = sDrafts.subscribe()
Expand All @@ -385,9 +392,6 @@ describe('queryStore', () => {

unsubDrafts()
unsubPublished()

draftsInstance.dispose()
publishedInstance.dispose()
})

it('separates cache entries by explicit perspective in options', async () => {
Expand All @@ -403,10 +407,12 @@ describe('queryStore', () => {

const sDrafts = getQueryState<{_id: string}[]>(base, {
query: '*[_type == "movie"]',
source,
perspective: 'drafts',
})
const sPublished = getQueryState<{_id: string}[]>(base, {
query: '*[_type == "movie"]',
source,
perspective: 'published',
})

Expand Down
Loading
Loading