@sanity/sdk 2.9.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks-dts/utils.d.ts +295 -69
- package/dist/_chunks-es/_internal.js +3 -14
- package/dist/_chunks-es/_internal.js.map +1 -1
- package/dist/_chunks-es/createGroqSearchFilter.js +129 -59
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -1
- package/dist/_chunks-es/version.js +1 -1
- package/dist/_exports/_internal.d.ts +16 -2
- package/dist/_exports/_internal.js +3 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +275 -149
- package/dist/index.js.map +1 -1
- package/package.json +11 -15
- package/src/_exports/_internal.ts +1 -0
- package/src/_exports/index.ts +33 -2
- package/src/agent/agentActions.ts +21 -25
- package/src/client/clientStore.test.ts +24 -60
- package/src/client/clientStore.ts +49 -56
- package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
- package/src/comlink/node/actions/getOrCreateNode.test.ts +5 -2
- package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
- package/src/comlink/node/actions/releaseNode.test.ts +3 -3
- package/src/config/sanityConfig.ts +72 -13
- package/src/document/applyDocumentActions.test.ts +7 -7
- package/src/document/applyDocumentActions.ts +5 -5
- package/src/document/documentStore.test.ts +68 -62
- package/src/document/documentStore.ts +33 -38
- package/src/document/processActions.ts +2 -2
- package/src/document/reducers.ts +4 -4
- package/src/document/sharedListener.ts +5 -7
- package/src/organization/organization.test-d.ts +102 -0
- package/src/organization/organization.test.ts +138 -0
- package/src/organization/organization.ts +166 -0
- package/src/organizations/organizations.test-d.ts +77 -0
- package/src/organizations/organizations.test.ts +150 -0
- package/src/organizations/organizations.ts +132 -0
- package/src/presence/bifurTransport.test.ts +46 -6
- package/src/presence/bifurTransport.ts +13 -1
- package/src/presence/presenceStore.test.ts +101 -5
- package/src/presence/presenceStore.ts +96 -24
- package/src/preview/getPreviewState.ts +1 -1
- package/src/preview/previewProjectionUtils.test.ts +4 -4
- package/src/preview/previewProjectionUtils.ts +6 -7
- package/src/preview/resolvePreview.ts +5 -1
- package/src/project/project.test-d.ts +93 -0
- package/src/project/project.test.ts +108 -10
- package/src/project/project.ts +152 -26
- package/src/projection/getProjectionState.ts +4 -4
- package/src/projection/projectionStore.test.ts +2 -2
- package/src/projection/resolveProjection.ts +2 -2
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
- package/src/projection/subscribeToStateAndFetchBatches.ts +11 -15
- package/src/projects/projects.test-d.ts +38 -0
- package/src/projects/projects.test.ts +104 -38
- package/src/projects/projects.ts +74 -14
- package/src/query/queryStore.test.ts +12 -12
- package/src/query/queryStore.ts +10 -11
- package/src/query/reducers.ts +3 -3
- package/src/releases/getPerspectiveState.ts +5 -5
- package/src/releases/releasesStore.test.ts +6 -6
- package/src/releases/releasesStore.ts +9 -9
- package/src/store/createActionBinder.test.ts +31 -31
- package/src/store/createActionBinder.ts +43 -38
- package/src/store/createSanityInstance.ts +5 -6
- package/src/telemetry/devMode.test.ts +8 -0
- package/src/telemetry/devMode.ts +10 -9
- package/src/telemetry/initTelemetry.test.ts +0 -17
- package/src/telemetry/initTelemetry.ts +2 -12
- package/src/users/reducers.ts +3 -4
- package/src/utils/createFetcherStore.ts +6 -4
- package/src/utils/isImportError.test.ts +72 -0
- package/src/utils/isImportError.ts +34 -0
- package/src/utils/object.test.ts +95 -0
- package/src/utils/object.ts +142 -0
package/src/projects/projects.ts
CHANGED
|
@@ -1,35 +1,95 @@
|
|
|
1
1
|
import {switchMap} from 'rxjs'
|
|
2
2
|
|
|
3
3
|
import {getClientState} from '../client/clientStore'
|
|
4
|
+
import {type Project} from '../project/project'
|
|
5
|
+
import {type SanityInstance} from '../store/createSanityInstance'
|
|
6
|
+
import {type StateSource} from '../store/createStateSourceAction'
|
|
4
7
|
import {createFetcherStore} from '../utils/createFetcherStore'
|
|
5
8
|
|
|
6
9
|
const API_VERSION = 'v2025-02-19'
|
|
7
10
|
|
|
11
|
+
/** @public */
|
|
12
|
+
export interface ProjectsOptions<
|
|
13
|
+
IncludeMembers extends boolean = false,
|
|
14
|
+
IncludeFeatures extends boolean = true,
|
|
15
|
+
> {
|
|
16
|
+
organizationId?: string
|
|
17
|
+
includeMembers?: IncludeMembers
|
|
18
|
+
includeFeatures?: IncludeFeatures
|
|
19
|
+
onlyExplicitMembership?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeProjectsOptions(options?: ProjectsOptions<boolean, boolean>) {
|
|
23
|
+
return {
|
|
24
|
+
organizationId: options?.organizationId,
|
|
25
|
+
includeMembers: options?.includeMembers ?? false,
|
|
26
|
+
includeFeatures: options?.includeFeatures ?? true,
|
|
27
|
+
onlyExplicitMembership: options?.onlyExplicitMembership ?? false,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** @internal */
|
|
32
|
+
export function getProjectsCacheKey(
|
|
33
|
+
_instance: SanityInstance,
|
|
34
|
+
options?: ProjectsOptions<boolean, boolean>,
|
|
35
|
+
): string {
|
|
36
|
+
const {organizationId, includeMembers, includeFeatures, onlyExplicitMembership} =
|
|
37
|
+
normalizeProjectsOptions(options)
|
|
38
|
+
const orgKey = organizationId ? `:org:${organizationId}` : ''
|
|
39
|
+
const membersKey = includeMembers ? ':members' : ''
|
|
40
|
+
const featuresKey = includeFeatures ? ':features' : ''
|
|
41
|
+
const explicitKey = onlyExplicitMembership ? ':explicit' : ''
|
|
42
|
+
return `projects${orgKey}${membersKey}${featuresKey}${explicitKey}`
|
|
43
|
+
}
|
|
44
|
+
|
|
8
45
|
const projects = createFetcherStore({
|
|
9
46
|
name: 'Projects',
|
|
10
|
-
getKey:
|
|
11
|
-
|
|
12
|
-
const membersKey = options?.includeMembers === false ? ':no-members' : ''
|
|
13
|
-
return `projects${orgKey}${membersKey}`
|
|
14
|
-
},
|
|
15
|
-
fetcher: (instance) => (options?: {organizationId?: string; includeMembers?: boolean}) =>
|
|
47
|
+
getKey: getProjectsCacheKey,
|
|
48
|
+
fetcher: (instance) => (options?: ProjectsOptions<boolean, boolean>) =>
|
|
16
49
|
getClientState(instance, {
|
|
17
50
|
apiVersion: API_VERSION,
|
|
18
51
|
scope: 'global',
|
|
19
|
-
requestTagPrefix: 'sanity.sdk.projects',
|
|
20
52
|
}).observable.pipe(
|
|
21
53
|
switchMap((client) => {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
54
|
+
const normalized = normalizeProjectsOptions(options)
|
|
55
|
+
const query = Object.fromEntries(
|
|
56
|
+
Object.entries(normalized)
|
|
57
|
+
.filter(([, value]) => value !== undefined)
|
|
58
|
+
.map(([key, value]) => [key, String(value)]),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return client.observable.request({
|
|
62
|
+
uri: '/projects',
|
|
63
|
+
query,
|
|
64
|
+
tag: 'projects.get',
|
|
27
65
|
})
|
|
28
66
|
}),
|
|
29
67
|
),
|
|
30
68
|
})
|
|
31
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Public signature for the projects state source. The conditional generics
|
|
72
|
+
* cannot flow through `BoundStoreAction`, so we declare the signature here
|
|
73
|
+
* and assign the (already-correct) runtime function to it.
|
|
74
|
+
*/
|
|
75
|
+
type GetProjectsState = <
|
|
76
|
+
IncludeMembers extends boolean = false,
|
|
77
|
+
IncludeFeatures extends boolean = true,
|
|
78
|
+
>(
|
|
79
|
+
instance: SanityInstance,
|
|
80
|
+
options?: ProjectsOptions<IncludeMembers, IncludeFeatures>,
|
|
81
|
+
) => StateSource<Project<IncludeMembers, IncludeFeatures>[] | undefined>
|
|
82
|
+
|
|
83
|
+
type ResolveProjects = <
|
|
84
|
+
IncludeMembers extends boolean = false,
|
|
85
|
+
IncludeFeatures extends boolean = true,
|
|
86
|
+
>(
|
|
87
|
+
instance: SanityInstance,
|
|
88
|
+
options?: ProjectsOptions<IncludeMembers, IncludeFeatures>,
|
|
89
|
+
) => Promise<Project<IncludeMembers, IncludeFeatures>[]>
|
|
90
|
+
|
|
32
91
|
/** @public */
|
|
33
|
-
export const getProjectsState = projects.getState
|
|
92
|
+
export const getProjectsState: GetProjectsState = projects.getState
|
|
93
|
+
|
|
34
94
|
/** @public */
|
|
35
|
-
export const resolveProjects = projects.resolveState
|
|
95
|
+
export const resolveProjects: ResolveProjects = projects.resolveState
|
|
@@ -3,7 +3,7 @@ import {delay, filter, firstValueFrom, Observable, of, Subject} from 'rxjs'
|
|
|
3
3
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
4
4
|
|
|
5
5
|
import {getClientState} from '../client/clientStore'
|
|
6
|
-
import {
|
|
6
|
+
import {isCanvasResource} from '../config/sanityConfig'
|
|
7
7
|
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
8
8
|
import {type StateSource} from '../store/createStateSourceAction'
|
|
9
9
|
import {getQueryState, resolveQuery} from './queryStore'
|
|
@@ -448,21 +448,21 @@ describe('queryStore', () => {
|
|
|
448
448
|
base.dispose()
|
|
449
449
|
})
|
|
450
450
|
|
|
451
|
-
it('uses
|
|
451
|
+
it('uses resource from params when passed in query options (listenForNewSubscribersAndFetch)', async () => {
|
|
452
452
|
const query = '*[_type == "movie"]'
|
|
453
453
|
const mediaLibrarySource = {mediaLibraryId: 'ml123'}
|
|
454
454
|
|
|
455
|
-
const state = getQueryState(instance, {query,
|
|
455
|
+
const state = getQueryState(instance, {query, resource: mediaLibrarySource})
|
|
456
456
|
const unsubscribe = state.subscribe()
|
|
457
457
|
|
|
458
458
|
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
459
459
|
|
|
460
|
-
// Verify getClientState was called with the
|
|
461
|
-
// This call includes projectId, dataset, and
|
|
460
|
+
// Verify getClientState was called with the resource from params in listenForNewSubscribersAndFetch
|
|
461
|
+
// This call includes projectId, dataset, and resource
|
|
462
462
|
expect(getClientState).toHaveBeenCalledWith(
|
|
463
463
|
instance,
|
|
464
464
|
expect.objectContaining({
|
|
465
|
-
|
|
465
|
+
resource: expect.objectContaining({
|
|
466
466
|
mediaLibraryId: 'ml123',
|
|
467
467
|
}),
|
|
468
468
|
}),
|
|
@@ -471,22 +471,22 @@ describe('queryStore', () => {
|
|
|
471
471
|
unsubscribe()
|
|
472
472
|
})
|
|
473
473
|
|
|
474
|
-
it('uses
|
|
474
|
+
it('uses resource from store context key when not a dataset resource (listenToLiveClientAndSetLastLiveEventIds)', async () => {
|
|
475
475
|
const query = '*[_type == "movie"]'
|
|
476
476
|
const canvasSource = {canvasId: 'canvas456'}
|
|
477
477
|
|
|
478
|
-
const state = getQueryState(instance, {query,
|
|
478
|
+
const state = getQueryState(instance, {query, resource: canvasSource})
|
|
479
479
|
const unsubscribe = state.subscribe()
|
|
480
480
|
|
|
481
481
|
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
482
482
|
|
|
483
|
-
// Verify getClientState was called with the canvas
|
|
484
|
-
// The
|
|
485
|
-
// This call only has apiVersion and
|
|
483
|
+
// Verify getClientState was called with the canvas resource for live events
|
|
484
|
+
// The resource is extracted from the store key and passed when it's not a dataset resource
|
|
485
|
+
// This call only has apiVersion and resource (no projectId/dataset)
|
|
486
486
|
const calls = vi.mocked(getClientState).mock.calls
|
|
487
487
|
const liveClientCall = calls.find(
|
|
488
488
|
([_instance, options]) =>
|
|
489
|
-
|
|
489
|
+
isCanvasResource(options.resource!) && options.resource.canvasId === 'canvas456',
|
|
490
490
|
)
|
|
491
491
|
expect(liveClientCall).toBeDefined()
|
|
492
492
|
|
package/src/query/queryStore.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
} from 'rxjs'
|
|
25
25
|
|
|
26
26
|
import {getClientState} from '../client/clientStore'
|
|
27
|
-
import {type DatasetHandle
|
|
27
|
+
import {type DatasetHandle} from '../config/sanityConfig'
|
|
28
28
|
/*
|
|
29
29
|
* Although this is an import dependency cycle, it is not a logical cycle:
|
|
30
30
|
* 1. queryStore uses getPerspectiveState when resolving release perspectives
|
|
@@ -35,7 +35,7 @@ import {type DatasetHandle, isDatasetSource} from '../config/sanityConfig'
|
|
|
35
35
|
// eslint-disable-next-line import/no-cycle
|
|
36
36
|
import {getPerspectiveState} from '../releases/getPerspectiveState'
|
|
37
37
|
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
38
|
-
import {
|
|
38
|
+
import {bindActionByResource, type BoundResourceKey} from '../store/createActionBinder'
|
|
39
39
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
40
40
|
import {
|
|
41
41
|
createStateSourceAction,
|
|
@@ -117,7 +117,7 @@ function normalizeOptionsWithPerspective(
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
const queryStore = defineStore<QueryStoreState,
|
|
120
|
+
const queryStore = defineStore<QueryStoreState, BoundResourceKey>({
|
|
121
121
|
name: 'QueryStore',
|
|
122
122
|
getInitialState: () => ({queries: {}}),
|
|
123
123
|
initialize(context) {
|
|
@@ -173,7 +173,7 @@ const listenForNewSubscribersAndFetch = ({state, instance}: StoreContext<QuerySt
|
|
|
173
173
|
projectId,
|
|
174
174
|
dataset,
|
|
175
175
|
tag,
|
|
176
|
-
|
|
176
|
+
resource,
|
|
177
177
|
perspective: perspectiveFromOptions,
|
|
178
178
|
...restOptions
|
|
179
179
|
} = parseQueryKey(group$.key)
|
|
@@ -190,7 +190,7 @@ const listenForNewSubscribersAndFetch = ({state, instance}: StoreContext<QuerySt
|
|
|
190
190
|
apiVersion: QUERY_STORE_API_VERSION,
|
|
191
191
|
projectId,
|
|
192
192
|
dataset,
|
|
193
|
-
|
|
193
|
+
resource,
|
|
194
194
|
}).observable
|
|
195
195
|
|
|
196
196
|
return combineLatest({
|
|
@@ -226,12 +226,11 @@ const listenForNewSubscribersAndFetch = ({state, instance}: StoreContext<QuerySt
|
|
|
226
226
|
const listenToLiveClientAndSetLastLiveEventIds = ({
|
|
227
227
|
state,
|
|
228
228
|
instance,
|
|
229
|
-
key: {
|
|
230
|
-
}: StoreContext<QueryStoreState,
|
|
229
|
+
key: {resource},
|
|
230
|
+
}: StoreContext<QueryStoreState, BoundResourceKey>) => {
|
|
231
231
|
const liveMessages$ = getClientState(instance, {
|
|
232
232
|
apiVersion: QUERY_STORE_API_VERSION,
|
|
233
|
-
|
|
234
|
-
...(source && !isDatasetSource(source) ? {source} : {}),
|
|
233
|
+
resource,
|
|
235
234
|
}).observable.pipe(
|
|
236
235
|
switchMap((client) =>
|
|
237
236
|
defer(() =>
|
|
@@ -316,7 +315,7 @@ export function getQueryState(
|
|
|
316
315
|
): ReturnType<typeof _getQueryState> {
|
|
317
316
|
return _getQueryState(...args)
|
|
318
317
|
}
|
|
319
|
-
const _getQueryState =
|
|
318
|
+
const _getQueryState = bindActionByResource(
|
|
320
319
|
queryStore,
|
|
321
320
|
createStateSourceAction({
|
|
322
321
|
selector: ({state, instance}: SelectorContext<QueryStoreState>, options: QueryOptions) => {
|
|
@@ -375,7 +374,7 @@ export function resolveQuery<TData>(
|
|
|
375
374
|
export function resolveQuery(...args: Parameters<typeof _resolveQuery>): Promise<unknown> {
|
|
376
375
|
return _resolveQuery(...args)
|
|
377
376
|
}
|
|
378
|
-
const _resolveQuery =
|
|
377
|
+
const _resolveQuery = bindActionByResource(
|
|
379
378
|
queryStore,
|
|
380
379
|
({state, instance}, {signal, ...options}: ResolveQueryOptions) => {
|
|
381
380
|
const normalized = normalizeOptionsWithPerspective(instance, options)
|
package/src/query/reducers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {omitProperty} from '../utils/object'
|
|
2
2
|
|
|
3
3
|
interface QueryState {
|
|
4
4
|
syncTags?: string[]
|
|
@@ -54,7 +54,7 @@ export const removeSubscriber =
|
|
|
54
54
|
const prevQuery = prev.queries[key]
|
|
55
55
|
if (!prevQuery) return prev
|
|
56
56
|
const subscribers = prevQuery.subscribers.filter((id) => id !== subscriptionId)
|
|
57
|
-
if (!subscribers.length) return {...prev, queries:
|
|
57
|
+
if (!subscribers.length) return {...prev, queries: omitProperty(prev.queries, key)}
|
|
58
58
|
return {...prev, queries: {...prev.queries, [key]: {...prevQuery, subscribers}}}
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -64,7 +64,7 @@ export const cancelQuery =
|
|
|
64
64
|
const prevQuery = prev.queries[key]
|
|
65
65
|
if (!prevQuery) return prev
|
|
66
66
|
if (prevQuery.subscribers.length) return prev
|
|
67
|
-
return {...prev, queries:
|
|
67
|
+
return {...prev, queries: omitProperty(prev.queries, key)}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export const initializeQuery =
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {createSelector} from 'reselect'
|
|
2
2
|
|
|
3
|
-
import {type
|
|
4
|
-
import {
|
|
3
|
+
import {type DocumentResource, type PerspectiveHandle} from '../config/sanityConfig'
|
|
4
|
+
import {bindActionByResource, type BoundStoreAction} from '../store/createActionBinder'
|
|
5
5
|
import {createStateSourceAction, type SelectorContext} from '../store/createStateSourceAction'
|
|
6
6
|
/*
|
|
7
7
|
* Although this is an import dependency cycle, it is not a logical cycle:
|
|
@@ -26,7 +26,7 @@ const selectActiveReleases = (context: SelectorContext<ReleasesStoreState>) =>
|
|
|
26
26
|
context.state.activeReleases
|
|
27
27
|
const selectOptions = (
|
|
28
28
|
_context: SelectorContext<ReleasesStoreState>,
|
|
29
|
-
options: PerspectiveHandle & {projectId?: string; dataset?: string;
|
|
29
|
+
options: PerspectiveHandle & {projectId?: string; dataset?: string; resource?: DocumentResource},
|
|
30
30
|
) => options
|
|
31
31
|
|
|
32
32
|
const memoizedOptionsSelector = createSelector(
|
|
@@ -105,11 +105,11 @@ let _boundGetPerspectiveState: BoundGetPerspectiveState | undefined
|
|
|
105
105
|
*/
|
|
106
106
|
export const getPerspectiveState: BoundGetPerspectiveState = (instance, ...rest) => {
|
|
107
107
|
if (!_boundGetPerspectiveState) {
|
|
108
|
-
_boundGetPerspectiveState =
|
|
108
|
+
_boundGetPerspectiveState = bindActionByResource(
|
|
109
109
|
releasesStore,
|
|
110
110
|
_getPerspectiveStateSelector,
|
|
111
111
|
) as BoundGetPerspectiveState
|
|
112
112
|
}
|
|
113
|
-
//
|
|
113
|
+
// bindActionByResource keyFn destructures { resource } from the first param, so pass {} when no options
|
|
114
114
|
return _boundGetPerspectiveState(instance, ...(rest.length ? rest : [{}]))
|
|
115
115
|
}
|
|
@@ -39,8 +39,8 @@ describe('releasesStore', () => {
|
|
|
39
39
|
expect.objectContaining({
|
|
40
40
|
query: 'releases::all()',
|
|
41
41
|
perspective: 'raw',
|
|
42
|
-
source: undefined,
|
|
43
42
|
tag: 'releases',
|
|
43
|
+
resource: {dataset: 'test', projectId: 'test'},
|
|
44
44
|
}),
|
|
45
45
|
)
|
|
46
46
|
})
|
|
@@ -73,7 +73,7 @@ describe('releasesStore', () => {
|
|
|
73
73
|
} as ReleaseDocument,
|
|
74
74
|
]
|
|
75
75
|
|
|
76
|
-
const state = getActiveReleasesState(instance, {
|
|
76
|
+
const state = getActiveReleasesState(instance, {resource: {projectId: 'test', dataset: 'test'}})
|
|
77
77
|
|
|
78
78
|
const [observer] = subscriber.mock.lastCall!
|
|
79
79
|
|
|
@@ -92,7 +92,7 @@ describe('releasesStore', () => {
|
|
|
92
92
|
observable: releasesSubject.asObservable(),
|
|
93
93
|
} as StateSource<ReleaseDocument[] | undefined>)
|
|
94
94
|
|
|
95
|
-
const state = getActiveReleasesState(instance, {
|
|
95
|
+
const state = getActiveReleasesState(instance, {resource: {projectId: 'test', dataset: 'test'}})
|
|
96
96
|
|
|
97
97
|
// Initial state should be default
|
|
98
98
|
expect(state.getCurrent()).toBeUndefined() // Default initial state
|
|
@@ -139,7 +139,7 @@ describe('releasesStore', () => {
|
|
|
139
139
|
observable: of([]),
|
|
140
140
|
} as StateSource<ReleaseDocument[] | undefined>)
|
|
141
141
|
|
|
142
|
-
const state = getActiveReleasesState(instance, {
|
|
142
|
+
const state = getActiveReleasesState(instance, {resource: {projectId: 'test', dataset: 'test'}})
|
|
143
143
|
|
|
144
144
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
145
145
|
|
|
@@ -153,7 +153,7 @@ describe('releasesStore', () => {
|
|
|
153
153
|
getCurrent: () => null as unknown as ReleaseDocument[] | undefined,
|
|
154
154
|
observable: of(null as unknown as ReleaseDocument[] | undefined),
|
|
155
155
|
} as StateSource<ReleaseDocument[] | undefined>)
|
|
156
|
-
const state = getActiveReleasesState(instance, {
|
|
156
|
+
const state = getActiveReleasesState(instance, {resource: {projectId: 'test', dataset: 'test'}})
|
|
157
157
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
158
158
|
expect(state.getCurrent()).toEqual([])
|
|
159
159
|
|
|
@@ -175,7 +175,7 @@ describe('releasesStore', () => {
|
|
|
175
175
|
observable: subject.asObservable(),
|
|
176
176
|
} as StateSource<ReleaseDocument[] | undefined>)
|
|
177
177
|
|
|
178
|
-
const state = getActiveReleasesState(instance, {
|
|
178
|
+
const state = getActiveReleasesState(instance, {resource: {projectId: 'test', dataset: 'test'}})
|
|
179
179
|
|
|
180
180
|
subject.error(new Error('Query failed'))
|
|
181
181
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {type SanityDocument} from '@sanity/types'
|
|
2
2
|
import {map} from 'rxjs'
|
|
3
3
|
|
|
4
|
-
import {type
|
|
4
|
+
import {type DocumentResource} from '../config/sanityConfig'
|
|
5
5
|
/*
|
|
6
6
|
* Although this is an import dependency cycle, it is not a logical cycle:
|
|
7
7
|
* 1. releasesStore uses queryStore as a data source
|
|
@@ -11,7 +11,7 @@ import {type DocumentSource, isDatasetSource} from '../config/sanityConfig'
|
|
|
11
11
|
*/
|
|
12
12
|
// eslint-disable-next-line import/no-cycle
|
|
13
13
|
import {getQueryState} from '../query/queryStore'
|
|
14
|
-
import {
|
|
14
|
+
import {bindActionByResource, type BoundResourceKey} from '../store/createActionBinder'
|
|
15
15
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
16
16
|
import {createStateSourceAction, type StateSource} from '../store/createStateSourceAction'
|
|
17
17
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
@@ -41,7 +41,7 @@ export interface ReleasesStoreState {
|
|
|
41
41
|
error?: unknown
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export const releasesStore = defineStore<ReleasesStoreState,
|
|
44
|
+
export const releasesStore = defineStore<ReleasesStoreState, BoundResourceKey>({
|
|
45
45
|
name: 'Releases',
|
|
46
46
|
getInitialState: (): ReleasesStoreState => ({
|
|
47
47
|
activeReleases: undefined,
|
|
@@ -56,7 +56,7 @@ export const releasesStore = defineStore<ReleasesStoreState, BoundSourceKey>({
|
|
|
56
56
|
* Get the active releases from the store.
|
|
57
57
|
* @internal
|
|
58
58
|
*/
|
|
59
|
-
const _getActiveReleasesState =
|
|
59
|
+
const _getActiveReleasesState = bindActionByResource(
|
|
60
60
|
releasesStore,
|
|
61
61
|
createStateSourceAction({
|
|
62
62
|
selector: ({state}, _?) => state.activeReleases,
|
|
@@ -69,9 +69,9 @@ const _getActiveReleasesState = bindActionBySource(
|
|
|
69
69
|
*/
|
|
70
70
|
export const getActiveReleasesState = (
|
|
71
71
|
instance: SanityInstance,
|
|
72
|
-
options?: {
|
|
72
|
+
options?: {resource?: DocumentResource},
|
|
73
73
|
): StateSource<ReleaseDocument[] | undefined> =>
|
|
74
|
-
//
|
|
74
|
+
// bindActionByResource keyFn destructures { resource } from the first param, so pass {} when no options
|
|
75
75
|
_getActiveReleasesState(instance, options ?? {})
|
|
76
76
|
|
|
77
77
|
const RELEASES_QUERY = 'releases::all()'
|
|
@@ -79,12 +79,12 @@ const RELEASES_QUERY = 'releases::all()'
|
|
|
79
79
|
const subscribeToReleases = ({
|
|
80
80
|
instance,
|
|
81
81
|
state,
|
|
82
|
-
key: {
|
|
83
|
-
}: StoreContext<ReleasesStoreState,
|
|
82
|
+
key: {resource},
|
|
83
|
+
}: StoreContext<ReleasesStoreState, BoundResourceKey>) => {
|
|
84
84
|
const {observable: releases$} = getQueryState<ReleaseDocument[]>(instance, {
|
|
85
85
|
query: RELEASES_QUERY,
|
|
86
86
|
perspective: 'raw',
|
|
87
|
-
|
|
87
|
+
resource,
|
|
88
88
|
tag: 'releases',
|
|
89
89
|
})
|
|
90
90
|
return releases$
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {type
|
|
3
|
+
import {type DocumentResource} from '../config/sanityConfig'
|
|
4
4
|
import {
|
|
5
5
|
bindActionByDataset,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
bindActionByResource,
|
|
7
|
+
bindActionByResourceAndPerspective,
|
|
8
8
|
bindActionGlobally,
|
|
9
9
|
createActionBinder,
|
|
10
10
|
} from './createActionBinder'
|
|
@@ -161,28 +161,28 @@ describe('bindActionGlobally', () => {
|
|
|
161
161
|
})
|
|
162
162
|
})
|
|
163
163
|
|
|
164
|
-
describe('
|
|
165
|
-
it('should throw an error when provided an invalid
|
|
164
|
+
describe('bindActionByResource', () => {
|
|
165
|
+
it('should throw an error when provided an invalid resource', () => {
|
|
166
166
|
const storeDefinition = {
|
|
167
167
|
name: 'SourceStore',
|
|
168
168
|
getInitialState: () => ({counter: 0}),
|
|
169
169
|
}
|
|
170
170
|
const action = vi.fn((_context) => 'success')
|
|
171
|
-
const boundAction =
|
|
171
|
+
const boundAction = bindActionByResource(storeDefinition, action)
|
|
172
172
|
const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
|
|
173
173
|
|
|
174
174
|
expect(() =>
|
|
175
|
-
boundAction(instance, {
|
|
176
|
-
).toThrow('Received invalid
|
|
175
|
+
boundAction(instance, {resource: {invalid: 'resource'} as unknown as DocumentResource}),
|
|
176
|
+
).toThrow('Received invalid resource:')
|
|
177
177
|
})
|
|
178
178
|
|
|
179
|
-
it('should throw an error when no
|
|
179
|
+
it('should throw an error when no resource provided and projectId/dataset are missing', () => {
|
|
180
180
|
const storeDefinition = {
|
|
181
181
|
name: 'SourceStore',
|
|
182
182
|
getInitialState: () => ({counter: 0}),
|
|
183
183
|
}
|
|
184
184
|
const action = vi.fn((_context) => 'success')
|
|
185
|
-
const boundAction =
|
|
185
|
+
const boundAction = bindActionByResource(storeDefinition, action)
|
|
186
186
|
const instance = createSanityInstance({projectId: '', dataset: ''})
|
|
187
187
|
|
|
188
188
|
expect(() => boundAction(instance, {})).toThrow(
|
|
@@ -190,47 +190,47 @@ describe('bindActionBySource', () => {
|
|
|
190
190
|
)
|
|
191
191
|
})
|
|
192
192
|
|
|
193
|
-
it('should work correctly with a valid dataset
|
|
193
|
+
it('should work correctly with a valid dataset resource', () => {
|
|
194
194
|
const storeDefinition = {
|
|
195
195
|
name: 'SourceStore',
|
|
196
196
|
getInitialState: () => ({counter: 0}),
|
|
197
197
|
}
|
|
198
198
|
const action = vi.fn((_context) => 'success')
|
|
199
|
-
const boundAction =
|
|
199
|
+
const boundAction = bindActionByResource(storeDefinition, action)
|
|
200
200
|
const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
|
|
201
201
|
|
|
202
202
|
const result = boundAction(instance, {
|
|
203
|
-
|
|
203
|
+
resource: {projectId: 'proj2', dataset: 'ds2'},
|
|
204
204
|
})
|
|
205
205
|
expect(result).toBe('success')
|
|
206
206
|
})
|
|
207
207
|
})
|
|
208
208
|
|
|
209
|
-
describe('
|
|
210
|
-
it('should throw an error when provided an invalid
|
|
209
|
+
describe('bindActionByResourceAndPerspective', () => {
|
|
210
|
+
it('should throw an error when provided an invalid resource', () => {
|
|
211
211
|
const storeDefinition = {
|
|
212
212
|
name: 'PerspectiveStore',
|
|
213
213
|
getInitialState: () => ({counter: 0}),
|
|
214
214
|
}
|
|
215
215
|
const action = vi.fn((_context) => 'success')
|
|
216
|
-
const boundAction =
|
|
216
|
+
const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
|
|
217
217
|
const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
|
|
218
218
|
|
|
219
219
|
expect(() =>
|
|
220
220
|
boundAction(instance, {
|
|
221
|
-
|
|
221
|
+
resource: {invalid: 'resource'} as unknown as DocumentResource,
|
|
222
222
|
perspective: 'drafts',
|
|
223
223
|
}),
|
|
224
|
-
).toThrow('Received invalid
|
|
224
|
+
).toThrow('Received invalid resource:')
|
|
225
225
|
})
|
|
226
226
|
|
|
227
|
-
it('should throw an error when no
|
|
227
|
+
it('should throw an error when no resource provided and projectId/dataset are missing', () => {
|
|
228
228
|
const storeDefinition = {
|
|
229
229
|
name: 'PerspectiveStore',
|
|
230
230
|
getInitialState: () => ({counter: 0}),
|
|
231
231
|
}
|
|
232
232
|
const action = vi.fn((_context) => 'success')
|
|
233
|
-
const boundAction =
|
|
233
|
+
const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
|
|
234
234
|
const instance = createSanityInstance({projectId: '', dataset: ''})
|
|
235
235
|
|
|
236
236
|
expect(() => boundAction(instance, {perspective: 'drafts'})).toThrow(
|
|
@@ -238,33 +238,33 @@ describe('bindActionBySourceAndPerspective', () => {
|
|
|
238
238
|
)
|
|
239
239
|
})
|
|
240
240
|
|
|
241
|
-
it('should work correctly with a valid dataset
|
|
241
|
+
it('should work correctly with a valid dataset resource and explicit perspective', () => {
|
|
242
242
|
const storeDefinition = {
|
|
243
243
|
name: 'PerspectiveStore',
|
|
244
244
|
getInitialState: () => ({counter: 0}),
|
|
245
245
|
}
|
|
246
246
|
const action = vi.fn((_context) => 'success')
|
|
247
|
-
const boundAction =
|
|
247
|
+
const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
|
|
248
248
|
const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
|
|
249
249
|
|
|
250
250
|
const result = boundAction(instance, {
|
|
251
|
-
|
|
251
|
+
resource: {projectId: 'proj2', dataset: 'ds2'},
|
|
252
252
|
perspective: 'drafts',
|
|
253
253
|
})
|
|
254
254
|
expect(result).toBe('success')
|
|
255
255
|
})
|
|
256
256
|
|
|
257
|
-
it('should work correctly with valid dataset
|
|
257
|
+
it('should work correctly with valid dataset resource and no perspective (falls back to drafts)', () => {
|
|
258
258
|
const storeDefinition = {
|
|
259
259
|
name: 'PerspectiveStore',
|
|
260
260
|
getInitialState: () => ({counter: 0}),
|
|
261
261
|
}
|
|
262
262
|
const action = vi.fn((_context) => 'success')
|
|
263
|
-
const boundAction =
|
|
263
|
+
const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
|
|
264
264
|
const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
|
|
265
265
|
|
|
266
266
|
const result = boundAction(instance, {
|
|
267
|
-
|
|
267
|
+
resource: {projectId: 'proj1', dataset: 'ds1'},
|
|
268
268
|
})
|
|
269
269
|
expect(result).toBe('success')
|
|
270
270
|
})
|
|
@@ -275,7 +275,7 @@ describe('bindActionBySourceAndPerspective', () => {
|
|
|
275
275
|
getInitialState: () => ({counter: 0}),
|
|
276
276
|
}
|
|
277
277
|
const action = vi.fn((context) => context.key)
|
|
278
|
-
const boundAction =
|
|
278
|
+
const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
|
|
279
279
|
const instance = createSanityInstance({
|
|
280
280
|
projectId: 'proj1',
|
|
281
281
|
dataset: 'ds1',
|
|
@@ -300,7 +300,7 @@ describe('bindActionBySourceAndPerspective', () => {
|
|
|
300
300
|
context.state.counter += increment
|
|
301
301
|
return context.state.counter
|
|
302
302
|
})
|
|
303
|
-
const boundAction =
|
|
303
|
+
const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
|
|
304
304
|
// Use unique project/dataset so we don't reuse stores from other tests
|
|
305
305
|
const instance = createSanityInstance({
|
|
306
306
|
projectId: 'perspective-isolation',
|
|
@@ -322,7 +322,7 @@ describe('bindActionBySourceAndPerspective', () => {
|
|
|
322
322
|
getInitialState: () => ({counter: 0}),
|
|
323
323
|
}
|
|
324
324
|
const action = vi.fn((_context) => 'success')
|
|
325
|
-
const boundAction =
|
|
325
|
+
const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
|
|
326
326
|
const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
|
|
327
327
|
|
|
328
328
|
const result = boundAction(instance, {
|
|
@@ -339,7 +339,7 @@ describe('bindActionBySourceAndPerspective', () => {
|
|
|
339
339
|
)
|
|
340
340
|
})
|
|
341
341
|
|
|
342
|
-
it('should reuse same store when same
|
|
342
|
+
it('should reuse same store when same resource and perspective are used', () => {
|
|
343
343
|
const storeDefinition = {
|
|
344
344
|
name: 'PerspectiveStore',
|
|
345
345
|
getInitialState: () => ({counter: 0}),
|
|
@@ -348,7 +348,7 @@ describe('bindActionBySourceAndPerspective', () => {
|
|
|
348
348
|
context.state.counter += increment
|
|
349
349
|
return context.state.counter
|
|
350
350
|
})
|
|
351
|
-
const boundAction =
|
|
351
|
+
const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
|
|
352
352
|
// Use unique project/dataset so we don't reuse stores from other tests
|
|
353
353
|
const instance = createSanityInstance({
|
|
354
354
|
projectId: 'perspective-reuse',
|