@sanity/sdk 3.0.0-rc.0 → 3.0.0-rc.1
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/createGroqSearchFilter.d.ts +1147 -0
- package/dist/_chunks-dts/createGroqSearchFilter.d.ts.map +1 -0
- package/dist/_chunks-es/_internal.js +134 -0
- package/dist/_chunks-es/_internal.js.map +1 -0
- package/dist/_chunks-es/createGroqSearchFilter.js +1864 -0
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -0
- package/dist/_chunks-es/telemetryManager.js +93 -0
- package/dist/_chunks-es/telemetryManager.js.map +1 -0
- package/dist/_exports/_internal.d.ts +95 -0
- package/dist/_exports/_internal.d.ts.map +1 -0
- package/dist/_exports/_internal.js +24 -0
- package/dist/_exports/_internal.js.map +1 -0
- package/dist/index.d.ts +1079 -1076
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1967 -2302
- package/dist/index.js.map +1 -1
- package/package.json +48 -47
- package/src/_exports/_internal.ts +26 -0
- package/src/_exports/index.ts +120 -11
- package/src/access/checkPermissions.test.ts +55 -0
- package/src/access/checkPermissions.ts +54 -0
- package/src/agent/agentActions.test.ts +16 -60
- package/src/agent/agentActions.ts +25 -38
- package/src/applications/applications.test-d.ts +83 -0
- package/src/applications/applications.test.ts +130 -0
- package/src/applications/applications.ts +364 -0
- package/src/applications/userApplications.test.ts +126 -0
- package/src/applications/userApplications.ts +261 -0
- package/src/auth/authLogger.ts +30 -0
- package/src/auth/authMode.test.ts +25 -0
- package/src/auth/authMode.ts +6 -3
- package/src/auth/authStore.test.ts +194 -13
- package/src/auth/authStore.ts +59 -26
- package/src/auth/getOrganizationVerificationState.test.ts +18 -17
- package/src/auth/getOrganizationVerificationState.ts +4 -4
- package/src/auth/handleAuthCallback.test.ts +35 -1
- package/src/auth/handleAuthCallback.ts +25 -6
- package/src/auth/logout.test.ts +75 -2
- package/src/auth/logout.ts +23 -4
- package/src/auth/refreshStampedToken.test.ts +219 -469
- package/src/auth/refreshStampedToken.ts +79 -195
- package/src/auth/studioAuth.ts +1 -1
- package/src/auth/studioModeAuth.ts +2 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +146 -13
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +37 -6
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +2 -0
- package/src/client/clientStore.test.ts +62 -37
- package/src/client/clientStore.ts +45 -75
- package/src/client/liveEvents.test.ts +151 -0
- package/src/client/liveEvents.ts +107 -0
- package/src/comlink/controller/actions/destroyController.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
- package/src/comlink/controller/actions/getOrCreateController.test.ts +4 -1
- package/src/comlink/controller/actions/releaseChannel.test.ts +1 -1
- package/src/comlink/controller/comlinkControllerStore.test.ts +4 -1
- package/src/comlink/node/actions/getOrCreateNode.test.ts +9 -3
- package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
- package/src/comlink/node/actions/releaseNode.test.ts +7 -4
- package/src/comlink/node/comlinkNodeStore.test.ts +2 -2
- package/src/comlink/node/getNodeState.test.ts +1 -1
- package/src/comlink/node/getNodeState.ts +2 -1
- package/src/config/__tests__/handles.test.ts +18 -12
- package/src/config/handles.ts +25 -7
- package/src/config/sanityConfig.ts +109 -79
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +10 -4
- package/src/document/actions.test.ts +115 -33
- package/src/document/actions.ts +212 -15
- package/src/document/applyDocumentActions.test.ts +38 -12
- package/src/document/applyDocumentActions.ts +30 -9
- package/src/document/documentConstants.ts +7 -0
- package/src/document/documentStore.concurrency.test.ts +1306 -0
- package/src/document/documentStore.test.ts +673 -278
- package/src/document/documentStore.ts +162 -71
- package/src/document/events.test.ts +57 -2
- package/src/document/events.ts +82 -26
- package/src/document/listen.test.ts +56 -0
- package/src/document/listen.ts +74 -21
- package/src/document/listenerEventOperators.test.ts +311 -0
- package/src/document/listenerEventOperators.ts +217 -0
- package/src/document/patchOperations.test.ts +49 -2
- package/src/document/patchOperations.ts +50 -0
- package/src/document/permissions.test.ts +105 -60
- package/src/document/permissions.ts +44 -19
- package/src/document/processActions/create.ts +138 -0
- package/src/document/processActions/delete.ts +100 -0
- package/src/document/processActions/discard.ts +63 -0
- package/src/document/processActions/edit.ts +154 -0
- package/src/document/processActions/processActions.ts +218 -0
- package/src/document/processActions/publish.ts +120 -0
- package/src/document/processActions/releaseArchive.ts +77 -0
- package/src/document/processActions/releaseCreate.ts +59 -0
- package/src/document/processActions/releaseDelete.ts +65 -0
- package/src/document/processActions/releaseEdit.ts +37 -0
- package/src/document/processActions/releasePublish.ts +45 -0
- package/src/document/processActions/releaseSchedule.ts +87 -0
- package/src/document/processActions/releaseUtil.ts +31 -0
- package/src/document/processActions/shared.ts +308 -0
- package/src/document/processActions/unpublish.ts +85 -0
- package/src/document/processActions.test.ts +1129 -121
- package/src/document/reducers.test.ts +639 -64
- package/src/document/reducers.ts +187 -48
- package/src/document/resourceRules.test.ts +178 -0
- package/src/document/resourceRules.ts +117 -0
- package/src/document/sharedListener.test.ts +16 -14
- package/src/document/sharedListener.ts +24 -8
- package/src/document/util.ts +14 -0
- package/src/favorites/favorites.test.ts +5 -6
- package/src/installations/installations.test-d.ts +68 -0
- package/src/installations/installations.test.ts +62 -0
- package/src/installations/installations.ts +202 -0
- package/src/organization/organization.test-d.ts +36 -0
- package/src/organization/organization.test.ts +138 -0
- package/src/organization/organization.ts +138 -0
- package/src/organizations/organizations.test-d.ts +56 -0
- package/src/organizations/organizations.test.ts +150 -0
- package/src/organizations/organizations.ts +107 -0
- package/src/presence/bifurTransport.test.ts +46 -6
- package/src/presence/bifurTransport.ts +19 -2
- package/src/presence/presenceStore.test.ts +94 -23
- package/src/presence/presenceStore.ts +85 -29
- package/src/preview/getPreviewState.test.ts +137 -0
- package/src/preview/getPreviewState.ts +69 -0
- package/src/preview/previewProjectionUtils.test.ts +9 -22
- package/src/preview/previewProjectionUtils.ts +7 -3
- package/src/preview/resolvePreview.test.ts +64 -0
- package/src/preview/resolvePreview.ts +43 -0
- package/src/preview/types.ts +10 -0
- package/src/project/project.test-d.ts +37 -0
- package/src/project/project.test.ts +157 -12
- package/src/project/project.ts +138 -25
- package/src/projection/getProjectionState.test.ts +18 -20
- package/src/projection/getProjectionState.ts +23 -3
- package/src/projection/projectionQuery.ts +5 -5
- package/src/projection/projectionStore.test.ts +1 -1
- package/src/projection/resolveProjection.test.ts +1 -2
- package/src/projection/resolveProjection.ts +16 -0
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
- package/src/projection/subscribeToStateAndFetchBatches.ts +32 -23
- package/src/projection/types.ts +8 -0
- package/src/projects/projects.test-d.ts +32 -0
- package/src/projects/projects.test.ts +108 -42
- package/src/projects/projects.ts +63 -27
- package/src/query/queryStore.test.ts +259 -123
- package/src/query/queryStore.ts +78 -63
- package/src/query/reducers.test.ts +0 -42
- package/src/query/reducers.ts +2 -18
- package/src/releases/getPerspectiveState.test.ts +17 -23
- package/src/releases/getPerspectiveState.ts +8 -20
- package/src/releases/observeReleases.test.ts +127 -0
- package/src/releases/observeReleases.ts +102 -0
- package/src/releases/releasesStore.test.ts +100 -53
- package/src/releases/releasesStore.ts +69 -44
- package/src/releases/utils/sortReleases.test.ts +38 -2
- package/src/releases/utils/sortReleases.ts +15 -10
- package/src/store/createActionBinder.test.ts +87 -90
- package/src/store/createActionBinder.ts +79 -25
- package/src/store/createSanityInstance.test.ts +83 -13
- package/src/store/createSanityInstance.ts +77 -2
- package/src/store/createStateSourceAction.test.ts +64 -2
- package/src/store/createStateSourceAction.ts +39 -44
- package/src/store/createStoreInstance.test.ts +4 -2
- package/src/store/fetcherStore.test-d.ts +121 -0
- package/src/store/fetcherStore.test.ts +299 -0
- package/src/store/fetcherStore.ts +801 -0
- package/src/telemetry/environment.test.ts +119 -0
- package/src/telemetry/environment.ts +92 -0
- package/src/telemetry/events.ts +42 -0
- package/src/telemetry/initTelemetry.test.ts +432 -0
- package/src/telemetry/initTelemetry.ts +218 -0
- package/src/telemetry/telemetryManager.test.ts +327 -0
- package/src/telemetry/telemetryManager.ts +199 -0
- package/src/users/reducers.test.ts +6 -1
- package/src/users/reducers.ts +5 -6
- package/src/users/types.ts +4 -4
- package/src/users/usersStore.test.ts +16 -12
- package/src/users/usersStore.ts +5 -1
- package/src/utils/buildQuery.test.ts +14 -0
- package/src/utils/buildQuery.ts +17 -0
- package/src/utils/createFetcherStore.test.ts +7 -5
- package/src/utils/createFetcherStore.ts +8 -5
- package/src/utils/getStagingApiHost.test.ts +21 -0
- package/src/utils/getStagingApiHost.ts +14 -0
- package/src/utils/ids.test.ts +1 -29
- package/src/utils/ids.ts +0 -10
- package/src/utils/includeShape.ts +36 -0
- package/src/utils/isImportError.test.ts +72 -0
- package/src/utils/isImportError.ts +34 -0
- package/src/utils/logger.test.ts +12 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/object.test.ts +95 -0
- package/src/utils/object.ts +142 -0
- package/src/utils/setCleanupTimeout.ts +37 -0
- package/src/document/processActions.ts +0 -707
- package/src/utils/defineIntent.test.ts +0 -477
- package/src/utils/defineIntent.ts +0 -244
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {DocumentId} from '@sanity/id-utils'
|
|
2
2
|
import {
|
|
3
3
|
combineLatest,
|
|
4
4
|
debounceTime,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import {getQueryState, resolveQuery} from '../query/queryStore'
|
|
20
20
|
import {type BoundPerspectiveKey} from '../store/createActionBinder'
|
|
21
21
|
import {type StoreContext} from '../store/defineStore'
|
|
22
|
+
import {isDeepEqual} from '../utils/object'
|
|
22
23
|
import {
|
|
23
24
|
createProjectionQuery,
|
|
24
25
|
processProjectionQuery,
|
|
@@ -45,18 +46,18 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
45
46
|
}: StoreContext<ProjectionStoreState, BoundPerspectiveKey>): Subscription => {
|
|
46
47
|
const documentProjections$ = state.observable.pipe(
|
|
47
48
|
map((s) => s.documentProjections),
|
|
48
|
-
distinctUntilChanged(
|
|
49
|
+
distinctUntilChanged(isDeepEqual),
|
|
49
50
|
)
|
|
50
51
|
|
|
51
52
|
const activeDocumentIds$ = state.observable.pipe(
|
|
52
|
-
map(({subscriptions}) => new Set(Object.keys(subscriptions))),
|
|
53
|
+
map(({subscriptions}) => new Set(Object.keys(subscriptions).map((id) => DocumentId(id)))),
|
|
53
54
|
distinctUntilChanged(isSetEqual),
|
|
54
55
|
)
|
|
55
56
|
|
|
56
57
|
const pendingUpdateSubscription = activeDocumentIds$
|
|
57
58
|
.pipe(
|
|
58
59
|
debounceTime(BATCH_DEBOUNCE_TIME),
|
|
59
|
-
startWith(new Set<
|
|
60
|
+
startWith(new Set<DocumentId>()),
|
|
60
61
|
pairwise(),
|
|
61
62
|
tap(([prevIds, currIds]) => {
|
|
62
63
|
const newIds = [...currIds].filter((id) => !prevIds.has(id))
|
|
@@ -88,7 +89,7 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
88
89
|
|
|
89
90
|
const queryTrigger$ = combineLatest([activeDocumentIds$, documentProjections$]).pipe(
|
|
90
91
|
debounceTime(BATCH_DEBOUNCE_TIME),
|
|
91
|
-
distinctUntilChanged(
|
|
92
|
+
distinctUntilChanged(isDeepEqual),
|
|
92
93
|
)
|
|
93
94
|
|
|
94
95
|
const queryExecutionSubscription = queryTrigger$
|
|
@@ -105,10 +106,12 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
105
106
|
|
|
106
107
|
const projectionQuery$ = new Observable<ProjectionQueryResult[]>((observer) => {
|
|
107
108
|
const {getCurrent, observable} = getQueryState<ProjectionQueryResult[]>(instance, {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
...{
|
|
110
|
+
query,
|
|
111
|
+
params,
|
|
112
|
+
tag: PROJECTION_TAG,
|
|
113
|
+
perspective,
|
|
114
|
+
},
|
|
112
115
|
resource,
|
|
113
116
|
})
|
|
114
117
|
|
|
@@ -116,11 +119,13 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
116
119
|
if (getCurrent() === undefined) {
|
|
117
120
|
return from(
|
|
118
121
|
resolveQuery<ProjectionQueryResult[]>(instance, {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
...{
|
|
123
|
+
query,
|
|
124
|
+
params,
|
|
125
|
+
tag: PROJECTION_TAG,
|
|
126
|
+
signal: controller.signal,
|
|
127
|
+
perspective,
|
|
128
|
+
},
|
|
124
129
|
resource,
|
|
125
130
|
}),
|
|
126
131
|
).pipe(switchMap(() => observable))
|
|
@@ -140,10 +145,12 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
140
145
|
|
|
141
146
|
const statusQuery$ = new Observable<StatusQueryResult[]>((observer) => {
|
|
142
147
|
const {getCurrent, observable} = getQueryState<StatusQueryResult[]>(instance, {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
...{
|
|
149
|
+
query: statusQuery,
|
|
150
|
+
params: statusParams,
|
|
151
|
+
tag: PROJECTION_TAG,
|
|
152
|
+
perspective: 'raw',
|
|
153
|
+
},
|
|
147
154
|
resource,
|
|
148
155
|
})
|
|
149
156
|
|
|
@@ -151,11 +158,13 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
151
158
|
if (getCurrent() === undefined) {
|
|
152
159
|
return from(
|
|
153
160
|
resolveQuery<StatusQueryResult[]>(instance, {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
161
|
+
...{
|
|
162
|
+
query: statusQuery,
|
|
163
|
+
params: statusParams,
|
|
164
|
+
tag: PROJECTION_TAG,
|
|
165
|
+
signal: controller.signal,
|
|
166
|
+
perspective: 'raw',
|
|
167
|
+
},
|
|
159
168
|
resource,
|
|
160
169
|
}),
|
|
161
170
|
).pipe(switchMap(() => observable))
|
package/src/projection/types.ts
CHANGED
|
@@ -11,6 +11,14 @@ export interface DocumentProjectionValues<TValue extends object = object> {
|
|
|
11
11
|
[projectionHash: string]: ProjectionValuePending<TValue>
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @public
|
|
16
|
+
* @deprecated
|
|
17
|
+
* Template literals are a bit too limited, so this type is deprecated.
|
|
18
|
+
* Use `string` instead. Projection strings are validated at runtime.
|
|
19
|
+
*/
|
|
20
|
+
export type ValidProjection = string
|
|
21
|
+
|
|
14
22
|
export interface DocumentProjections {
|
|
15
23
|
[projectionHash: string]: string
|
|
16
24
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {expectTypeOf, test} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {type Project, type ProjectMember} from '../project/project'
|
|
4
|
+
import {type SanityInstance} from '../store/createSanityInstance'
|
|
5
|
+
import {type StateSource} from '../store/createStateSourceAction'
|
|
6
|
+
import {type FetcherSnapshot} from '../store/fetcherStore'
|
|
7
|
+
import {projects} from './projects'
|
|
8
|
+
|
|
9
|
+
const instance = {} as SanityInstance
|
|
10
|
+
|
|
11
|
+
test('projects.resolveState — resolves the wide-boolean list shape', () => {
|
|
12
|
+
expectTypeOf(projects.resolveState(instance)).resolves.toEqualTypeOf<
|
|
13
|
+
Project<boolean, boolean>[]
|
|
14
|
+
>()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('Project<boolean, boolean> — members and features are optional', () => {
|
|
18
|
+
type Item = Project<boolean, boolean>
|
|
19
|
+
expectTypeOf<Item['members']>().toEqualTypeOf<ProjectMember[] | undefined>()
|
|
20
|
+
expectTypeOf<Item['features']>().toEqualTypeOf<string[] | undefined>()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('projects.getState — exposes the snapshot envelope', () => {
|
|
24
|
+
expectTypeOf(projects.getState(instance)).toEqualTypeOf<
|
|
25
|
+
StateSource<FetcherSnapshot<Project<boolean, boolean>[]>>
|
|
26
|
+
>()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('projects — rejects non-boolean flag values', () => {
|
|
30
|
+
// @ts-expect-error includeMembers must be a boolean
|
|
31
|
+
void projects.resolveState(instance, {includeMembers: 'yes'})
|
|
32
|
+
})
|
|
@@ -5,7 +5,7 @@ import {afterEach, beforeEach, describe, it} from 'vitest'
|
|
|
5
5
|
import {getClientState} from '../client/clientStore'
|
|
6
6
|
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
7
7
|
import {type StateSource} from '../store/createStateSourceAction'
|
|
8
|
-
import {
|
|
8
|
+
import {getProjectsCacheKey, projects} from './projects'
|
|
9
9
|
|
|
10
10
|
vi.mock('../client/clientStore')
|
|
11
11
|
|
|
@@ -13,64 +13,130 @@ describe('projects', () => {
|
|
|
13
13
|
let instance: SanityInstance
|
|
14
14
|
|
|
15
15
|
beforeEach(() => {
|
|
16
|
-
instance = createSanityInstance()
|
|
16
|
+
instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
17
17
|
})
|
|
18
18
|
|
|
19
19
|
afterEach(() => {
|
|
20
20
|
instance.dispose()
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
-
it('calls
|
|
24
|
-
const
|
|
25
|
-
const
|
|
23
|
+
it('calls `client.observable.request` against `/projects` and returns the result', async () => {
|
|
24
|
+
const list = [{id: 'a'}, {id: 'b'}]
|
|
25
|
+
const request = vi.fn().mockReturnValue(of(list))
|
|
26
26
|
|
|
27
27
|
const mockClient = {
|
|
28
|
-
observable: {
|
|
29
|
-
projects: {list} as unknown as SanityClient['observable']['projects'],
|
|
30
|
-
},
|
|
28
|
+
observable: {request} as unknown as SanityClient['observable'],
|
|
31
29
|
} as SanityClient
|
|
32
30
|
|
|
33
31
|
vi.mocked(getClientState).mockReturnValue({
|
|
34
32
|
observable: of(mockClient),
|
|
35
33
|
} as StateSource<SanityClient>)
|
|
36
34
|
|
|
37
|
-
const result = await
|
|
38
|
-
expect(result).toEqual(
|
|
39
|
-
expect(
|
|
35
|
+
const result = await projects.resolveState(instance)
|
|
36
|
+
expect(result).toEqual(list)
|
|
37
|
+
expect(request).toHaveBeenCalledWith({
|
|
38
|
+
uri: '/projects',
|
|
39
|
+
query: {includeMembers: 'false', includeFeatures: 'true', onlyExplicitMembership: 'false'},
|
|
40
|
+
tag: 'projects.list',
|
|
41
|
+
})
|
|
40
42
|
})
|
|
41
|
-
})
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const mockInstance = {} as SanityInstance
|
|
56
|
-
|
|
57
|
-
// Test default behavior (no options)
|
|
58
|
-
const defaultKey = mockGetKey(mockInstance)
|
|
59
|
-
expect(defaultKey).toBe('projects')
|
|
60
|
-
|
|
61
|
-
// Test with organizationId only
|
|
62
|
-
const orgKey = mockGetKey(mockInstance, {organizationId: 'org123'})
|
|
63
|
-
expect(orgKey).toBe('projects:org:org123')
|
|
64
|
-
|
|
65
|
-
// Test with includeMembers: false only
|
|
66
|
-
const noMembersKey = mockGetKey(mockInstance, {includeMembers: false})
|
|
67
|
-
expect(noMembersKey).toBe('projects:no-members')
|
|
68
|
-
|
|
69
|
-
// Test with both parameters
|
|
70
|
-
const bothKey = mockGetKey(mockInstance, {
|
|
44
|
+
it('serializes query params (booleans → strings) and omits undefined values', async () => {
|
|
45
|
+
const request = vi.fn().mockReturnValue(of([]))
|
|
46
|
+
const mockClient = {
|
|
47
|
+
observable: {request} as unknown as SanityClient['observable'],
|
|
48
|
+
} as SanityClient
|
|
49
|
+
|
|
50
|
+
vi.mocked(getClientState).mockReturnValue({
|
|
51
|
+
observable: of(mockClient),
|
|
52
|
+
} as StateSource<SanityClient>)
|
|
53
|
+
|
|
54
|
+
await projects.resolveState(instance, {
|
|
71
55
|
organizationId: 'org123',
|
|
72
|
-
includeMembers:
|
|
56
|
+
includeMembers: true,
|
|
57
|
+
includeFeatures: false,
|
|
73
58
|
})
|
|
74
|
-
|
|
59
|
+
|
|
60
|
+
expect(request).toHaveBeenCalledWith({
|
|
61
|
+
uri: '/projects',
|
|
62
|
+
query: {
|
|
63
|
+
organizationId: 'org123',
|
|
64
|
+
includeMembers: 'true',
|
|
65
|
+
includeFeatures: 'false',
|
|
66
|
+
onlyExplicitMembership: 'false',
|
|
67
|
+
},
|
|
68
|
+
tag: 'projects.list',
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe('projects cache key generation', () => {
|
|
74
|
+
let instance: SanityInstance
|
|
75
|
+
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
afterEach(() => {
|
|
81
|
+
instance.dispose()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('default call includes :features (default-true) and excludes :members (default-false)', () => {
|
|
85
|
+
expect(getProjectsCacheKey(instance)).toBe('projects:features')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('treats undefined and the matching default as the same key', () => {
|
|
89
|
+
expect(getProjectsCacheKey(instance)).toBe(
|
|
90
|
+
getProjectsCacheKey(instance, {includeMembers: false, includeFeatures: true}),
|
|
91
|
+
)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('treats raw and explicit defaults equivalently', () => {
|
|
95
|
+
expect(getProjectsCacheKey(instance, {organizationId: 'org123'})).toBe(
|
|
96
|
+
getProjectsCacheKey(instance, {
|
|
97
|
+
organizationId: 'org123',
|
|
98
|
+
includeMembers: false,
|
|
99
|
+
includeFeatures: true,
|
|
100
|
+
onlyExplicitMembership: false,
|
|
101
|
+
}),
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('explicit includeFeatures: false drops the :features segment', () => {
|
|
106
|
+
expect(getProjectsCacheKey(instance, {includeFeatures: false})).toBe('projects')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('appends an org segment when organizationId is set', () => {
|
|
110
|
+
expect(getProjectsCacheKey(instance, {organizationId: 'org123'})).toBe(
|
|
111
|
+
'projects:org:org123:features',
|
|
112
|
+
)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('appends :members when includeMembers is true', () => {
|
|
116
|
+
expect(getProjectsCacheKey(instance, {includeMembers: true})).toBe('projects:members:features')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('combines all segments in order', () => {
|
|
120
|
+
expect(
|
|
121
|
+
getProjectsCacheKey(instance, {
|
|
122
|
+
organizationId: 'org123',
|
|
123
|
+
includeMembers: true,
|
|
124
|
+
includeFeatures: true,
|
|
125
|
+
onlyExplicitMembership: true,
|
|
126
|
+
}),
|
|
127
|
+
).toBe('projects:org:org123:members:features:explicit')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('produces distinct keys for each meaningful option permutation', () => {
|
|
131
|
+
const keys = new Set([
|
|
132
|
+
getProjectsCacheKey(instance),
|
|
133
|
+
getProjectsCacheKey(instance, {includeMembers: true}),
|
|
134
|
+
getProjectsCacheKey(instance, {includeFeatures: false}),
|
|
135
|
+
getProjectsCacheKey(instance, {includeMembers: true, includeFeatures: false}),
|
|
136
|
+
getProjectsCacheKey(instance, {onlyExplicitMembership: true}),
|
|
137
|
+
getProjectsCacheKey(instance, {organizationId: 'a'}),
|
|
138
|
+
getProjectsCacheKey(instance, {organizationId: 'b'}),
|
|
139
|
+
])
|
|
140
|
+
expect(keys.size).toBe(7)
|
|
75
141
|
})
|
|
76
142
|
})
|
package/src/projects/projects.ts
CHANGED
|
@@ -1,35 +1,71 @@
|
|
|
1
1
|
import {switchMap} from 'rxjs'
|
|
2
2
|
|
|
3
3
|
import {getClientState} from '../client/clientStore'
|
|
4
|
-
import {
|
|
4
|
+
import {type Project} from '../project/project'
|
|
5
|
+
import {type SanityInstance} from '../store/createSanityInstance'
|
|
6
|
+
import {defineFetcher} from '../store/fetcherStore'
|
|
7
|
+
import {buildQuery} from '../utils/buildQuery'
|
|
5
8
|
|
|
6
9
|
const API_VERSION = 'v2025-02-19'
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Fetcher for the current user's projects (`GET /projects`), on the shared
|
|
47
|
+
* fetcher cache.
|
|
48
|
+
*
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
export const projects = defineFetcher<
|
|
52
|
+
[options?: ProjectsOptions<boolean, boolean>],
|
|
53
|
+
Project<boolean, boolean>[]
|
|
54
|
+
>({
|
|
55
|
+
name: 'projects',
|
|
56
|
+
getKey: getProjectsCacheKey,
|
|
57
|
+
fetch: (instance) => (options) =>
|
|
58
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
59
|
+
switchMap((client) =>
|
|
60
|
+
client.observable.request<Project<boolean, boolean>[]>({
|
|
61
|
+
uri: '/projects',
|
|
62
|
+
query: buildQuery(normalizeProjectsOptions(options)),
|
|
63
|
+
tag: 'projects.list',
|
|
64
|
+
}),
|
|
65
|
+
),
|
|
29
66
|
),
|
|
67
|
+
tags: (data) => [
|
|
68
|
+
{type: 'project', id: 'LIST'},
|
|
69
|
+
...data.map((project) => ({type: 'project', id: project.id})),
|
|
70
|
+
],
|
|
30
71
|
})
|
|
31
|
-
|
|
32
|
-
/** @public */
|
|
33
|
-
export const getProjectsState = projects.getState
|
|
34
|
-
/** @public */
|
|
35
|
-
export const resolveProjects = projects.resolveState
|