@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
|
@@ -7,14 +7,6 @@ import {type SanityInstance} from '../store/createSanityInstance'
|
|
|
7
7
|
|
|
8
8
|
const API_VERSION = 'vX'
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* Options that all agent actions accept for targeting a specific resource.
|
|
12
|
-
* @alpha
|
|
13
|
-
*/
|
|
14
|
-
export interface AgentResourceOptions {
|
|
15
|
-
resource: DocumentResource
|
|
16
|
-
}
|
|
17
|
-
|
|
18
10
|
/** @alpha */
|
|
19
11
|
export type AgentGenerateOptions = Parameters<
|
|
20
12
|
SanityClient['observable']['agent']['action']['generate']
|
|
@@ -66,13 +58,12 @@ export type AgentPatchResult = Awaited<ReturnType<SanityClient['agent']['action'
|
|
|
66
58
|
*/
|
|
67
59
|
export function agentGenerate(
|
|
68
60
|
instance: SanityInstance,
|
|
69
|
-
options: AgentGenerateOptions
|
|
61
|
+
options: AgentGenerateOptions,
|
|
62
|
+
resource?: DocumentResource,
|
|
70
63
|
): AgentGenerateResult {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
resource,
|
|
75
|
-
}).observable.pipe(switchMap((client) => client.observable.agent.action.generate(agentOptions)))
|
|
64
|
+
return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
|
|
65
|
+
switchMap((client) => client.observable.agent.action.generate(options)),
|
|
66
|
+
)
|
|
76
67
|
}
|
|
77
68
|
|
|
78
69
|
/**
|
|
@@ -84,13 +75,12 @@ export function agentGenerate(
|
|
|
84
75
|
*/
|
|
85
76
|
export function agentTransform(
|
|
86
77
|
instance: SanityInstance,
|
|
87
|
-
options: AgentTransformOptions
|
|
78
|
+
options: AgentTransformOptions,
|
|
79
|
+
resource?: DocumentResource,
|
|
88
80
|
): AgentTransformResult {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
resource,
|
|
93
|
-
}).observable.pipe(switchMap((client) => client.observable.agent.action.transform(agentOptions)))
|
|
81
|
+
return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
|
|
82
|
+
switchMap((client) => client.observable.agent.action.transform(options)),
|
|
83
|
+
)
|
|
94
84
|
}
|
|
95
85
|
|
|
96
86
|
/**
|
|
@@ -102,13 +92,12 @@ export function agentTransform(
|
|
|
102
92
|
*/
|
|
103
93
|
export function agentTranslate(
|
|
104
94
|
instance: SanityInstance,
|
|
105
|
-
options: AgentTranslateOptions
|
|
95
|
+
options: AgentTranslateOptions,
|
|
96
|
+
resource?: DocumentResource,
|
|
106
97
|
): AgentTranslateResult {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
resource,
|
|
111
|
-
}).observable.pipe(switchMap((client) => client.observable.agent.action.translate(agentOptions)))
|
|
98
|
+
return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
|
|
99
|
+
switchMap((client) => client.observable.agent.action.translate(options)),
|
|
100
|
+
)
|
|
112
101
|
}
|
|
113
102
|
|
|
114
103
|
/**
|
|
@@ -120,13 +109,12 @@ export function agentTranslate(
|
|
|
120
109
|
*/
|
|
121
110
|
export function agentPrompt(
|
|
122
111
|
instance: SanityInstance,
|
|
123
|
-
options: AgentPromptOptions
|
|
112
|
+
options: AgentPromptOptions,
|
|
113
|
+
resource?: DocumentResource,
|
|
124
114
|
): Observable<AgentPromptResult> {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
resource,
|
|
129
|
-
}).observable.pipe(switchMap((client) => from(client.agent.action.prompt(agentOptions))))
|
|
115
|
+
return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
|
|
116
|
+
switchMap((client) => from(client.agent.action.prompt(options))),
|
|
117
|
+
)
|
|
130
118
|
}
|
|
131
119
|
|
|
132
120
|
/**
|
|
@@ -138,11 +126,10 @@ export function agentPrompt(
|
|
|
138
126
|
*/
|
|
139
127
|
export function agentPatch(
|
|
140
128
|
instance: SanityInstance,
|
|
141
|
-
options: AgentPatchOptions
|
|
129
|
+
options: AgentPatchOptions,
|
|
130
|
+
resource?: DocumentResource,
|
|
142
131
|
): Observable<AgentPatchResult> {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
resource,
|
|
147
|
-
}).observable.pipe(switchMap((client) => from(client.agent.action.patch(agentOptions))))
|
|
132
|
+
return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
|
|
133
|
+
switchMap((client) => from(client.agent.action.patch(options))),
|
|
134
|
+
)
|
|
148
135
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {expectTypeOf, test} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {type SanityInstance} from '../store/createSanityInstance'
|
|
4
|
+
import {type StateSource} from '../store/createStateSourceAction'
|
|
5
|
+
import {type FetcherSnapshot} from '../store/fetcherStore'
|
|
6
|
+
import {
|
|
7
|
+
type Application,
|
|
8
|
+
application,
|
|
9
|
+
type ApplicationBase,
|
|
10
|
+
type ApplicationDeployment,
|
|
11
|
+
type ApplicationInclude,
|
|
12
|
+
type ApplicationInterface,
|
|
13
|
+
applications,
|
|
14
|
+
type ApplicationsResponse,
|
|
15
|
+
type ApplicationStudioConfig,
|
|
16
|
+
} from './applications'
|
|
17
|
+
|
|
18
|
+
const instance = {} as SanityInstance
|
|
19
|
+
|
|
20
|
+
type ChildKeys = 'config' | 'activeDeployment'
|
|
21
|
+
type DeploymentChildKeys = 'interfaces' | 'workspaces' | 'access'
|
|
22
|
+
|
|
23
|
+
// Whether key `K` of `T` is optional, without an `{}`-based check.
|
|
24
|
+
type KeyModifier<T, K extends keyof T> =
|
|
25
|
+
Pick<T, K> extends Required<Pick<T, K>> ? 'required' : 'optional'
|
|
26
|
+
|
|
27
|
+
test('Application — no includes: only the base shape, no config or activeDeployment', () => {
|
|
28
|
+
expectTypeOf<Application>().toEqualTypeOf<ApplicationBase>()
|
|
29
|
+
expectTypeOf<Application<never>>().toEqualTypeOf<ApplicationBase>()
|
|
30
|
+
expectTypeOf<Extract<keyof Application, ChildKeys>>().toEqualTypeOf<never>()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('Application — config.studio adds a required config.studio', () => {
|
|
34
|
+
expectTypeOf<
|
|
35
|
+
Application<'config.studio'>['config']['studio']
|
|
36
|
+
>().toEqualTypeOf<ApplicationStudioConfig>()
|
|
37
|
+
// mfManifest not requested → not present under config
|
|
38
|
+
expectTypeOf<
|
|
39
|
+
Extract<keyof Application<'config.studio'>['config'], 'mfManifest'>
|
|
40
|
+
>().toEqualTypeOf<never>()
|
|
41
|
+
// requesting only config does not pull in the deployment
|
|
42
|
+
expectTypeOf<
|
|
43
|
+
Extract<keyof Application<'config.studio'>, 'activeDeployment'>
|
|
44
|
+
>().toEqualTypeOf<never>()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('Application — both config tokens merge under a single config', () => {
|
|
48
|
+
type Config = Application<'config.studio' | 'config.mfManifest'>['config']
|
|
49
|
+
expectTypeOf<Config['studio']>().toEqualTypeOf<ApplicationStudioConfig>()
|
|
50
|
+
expectTypeOf<Config['mfManifest']>().toEqualTypeOf<unknown>()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('Application — activeDeployment token adds the deployment summary, no children', () => {
|
|
54
|
+
expectTypeOf<
|
|
55
|
+
Application<'activeDeployment'>['activeDeployment']
|
|
56
|
+
>().toEqualTypeOf<ApplicationDeployment<'activeDeployment'> | null>()
|
|
57
|
+
type Deployment = NonNullable<Application<'activeDeployment'>['activeDeployment']>
|
|
58
|
+
expectTypeOf<Extract<keyof Deployment, DeploymentChildKeys>>().toEqualTypeOf<never>()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('Application — a child token forces the deployment in and adds only that child', () => {
|
|
62
|
+
type Deployment = NonNullable<Application<'interfaces'>['activeDeployment']>
|
|
63
|
+
expectTypeOf<Deployment['interfaces']>().toEqualTypeOf<ApplicationInterface[]>()
|
|
64
|
+
expectTypeOf<Extract<keyof Deployment, 'workspaces' | 'access'>>().toEqualTypeOf<never>()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('Application — a wide include array makes config and activeDeployment optional', () => {
|
|
68
|
+
type Wide = Application<ApplicationInclude>
|
|
69
|
+
expectTypeOf<KeyModifier<Wide, 'activeDeployment'>>().toEqualTypeOf<'optional'>()
|
|
70
|
+
expectTypeOf<KeyModifier<Wide, 'config'>>().toEqualTypeOf<'optional'>()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('applications.resolveState — resolves the wide list envelope', () => {
|
|
74
|
+
expectTypeOf(
|
|
75
|
+
applications.resolveState(instance, {organizationId: 'org_1'}),
|
|
76
|
+
).resolves.toEqualTypeOf<ApplicationsResponse<ApplicationInclude>>()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('application.getState — exposes the snapshot envelope', () => {
|
|
80
|
+
expectTypeOf(application.getState(instance, 'app_1')).toEqualTypeOf<
|
|
81
|
+
StateSource<FetcherSnapshot<Application<ApplicationInclude>>>
|
|
82
|
+
>()
|
|
83
|
+
})
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {type SanityClient} from '@sanity/client'
|
|
2
|
+
import {of} from 'rxjs'
|
|
3
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {getClientState} from '../client/clientStore'
|
|
6
|
+
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
7
|
+
import {type StateSource} from '../store/createStateSourceAction'
|
|
8
|
+
import {defineMutation} from '../store/fetcherStore'
|
|
9
|
+
import {application, applications, deleteApplication, updateApplication} from './applications'
|
|
10
|
+
|
|
11
|
+
vi.mock('../client/clientStore')
|
|
12
|
+
|
|
13
|
+
describe('applications', () => {
|
|
14
|
+
let instance: SanityInstance
|
|
15
|
+
const request = vi.fn()
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
19
|
+
request.mockReset()
|
|
20
|
+
vi.mocked(getClientState).mockReturnValue({
|
|
21
|
+
observable: of({observable: {request}} as unknown as SanityClient),
|
|
22
|
+
} as StateSource<SanityClient>)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
instance.dispose()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('lists applications for an organization, serialising include as a sorted CSV', async () => {
|
|
30
|
+
const response = {nextCursor: null, data: [{id: 'app1'}]}
|
|
31
|
+
request.mockReturnValue(of(response))
|
|
32
|
+
|
|
33
|
+
const result = await applications.resolveState(instance, {
|
|
34
|
+
organizationId: 'org1',
|
|
35
|
+
include: ['workspaces', 'access'],
|
|
36
|
+
limit: 'none',
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
expect(result).toEqual(response)
|
|
40
|
+
expect(getClientState).toHaveBeenCalledWith(instance, {
|
|
41
|
+
apiVersion: 'vX',
|
|
42
|
+
scope: 'global',
|
|
43
|
+
})
|
|
44
|
+
expect(request).toHaveBeenCalledWith({
|
|
45
|
+
uri: '/applications',
|
|
46
|
+
query: {organizationId: 'org1', include: 'access,workspaces', limit: 'none'},
|
|
47
|
+
tag: 'applications.list',
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('fetches a single application by id', async () => {
|
|
52
|
+
const app = {id: 'app1', title: 'One'}
|
|
53
|
+
request.mockReturnValue(of(app))
|
|
54
|
+
|
|
55
|
+
const result = await application.resolveState(instance, 'app1')
|
|
56
|
+
|
|
57
|
+
expect(result).toEqual(app)
|
|
58
|
+
expect(request).toHaveBeenCalledWith({
|
|
59
|
+
uri: '/applications/app1',
|
|
60
|
+
query: {},
|
|
61
|
+
tag: 'applications.get',
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('refetches the list when a mutation invalidates the LIST tag', async () => {
|
|
66
|
+
let n = 0
|
|
67
|
+
request.mockImplementation(() => of({nextCursor: null, data: [{id: `app-${++n}`}]}))
|
|
68
|
+
|
|
69
|
+
const source = applications.getState(instance, {organizationId: 'org1'})
|
|
70
|
+
const unsubscribe = source.subscribe()
|
|
71
|
+
await vi.waitFor(() => expect(source.getCurrent().status).toBe('success'))
|
|
72
|
+
expect(request).toHaveBeenCalledTimes(1)
|
|
73
|
+
|
|
74
|
+
const install = defineMutation<undefined, {ok: boolean}>({
|
|
75
|
+
name: 'test-install-application',
|
|
76
|
+
mutationFn: () => () => of({ok: true}),
|
|
77
|
+
invalidates: [{type: 'application', id: 'LIST'}],
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const {invalidated} = await install(instance, undefined)
|
|
81
|
+
await invalidated
|
|
82
|
+
|
|
83
|
+
expect(request).toHaveBeenCalledTimes(2)
|
|
84
|
+
expect(source.getCurrent().data).toEqual({nextCursor: null, data: [{id: 'app-2'}]})
|
|
85
|
+
unsubscribe()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('updates an application via PATCH and refetches the invalidated list', async () => {
|
|
89
|
+
request.mockImplementation((opts) => {
|
|
90
|
+
const {method} = opts as {method?: string}
|
|
91
|
+
return method === 'PATCH'
|
|
92
|
+
? of({id: 'app1', title: 'New'})
|
|
93
|
+
: of({nextCursor: null, data: [{id: 'app1'}]})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const source = applications.getState(instance, {organizationId: 'org1'})
|
|
97
|
+
const unsubscribe = source.subscribe()
|
|
98
|
+
await vi.waitFor(() => expect(source.getCurrent().status).toBe('success'))
|
|
99
|
+
|
|
100
|
+
const {data, invalidated} = await updateApplication(instance, {
|
|
101
|
+
applicationId: 'app1',
|
|
102
|
+
title: 'New',
|
|
103
|
+
})
|
|
104
|
+
await invalidated
|
|
105
|
+
|
|
106
|
+
expect(data).toEqual({id: 'app1', title: 'New'})
|
|
107
|
+
expect(request).toHaveBeenCalledWith({
|
|
108
|
+
uri: '/applications/app1',
|
|
109
|
+
method: 'PATCH',
|
|
110
|
+
body: {title: 'New'},
|
|
111
|
+
tag: 'applications.update',
|
|
112
|
+
})
|
|
113
|
+
// initial list GET + PATCH + list refetch after invalidation
|
|
114
|
+
expect(request).toHaveBeenCalledTimes(3)
|
|
115
|
+
unsubscribe()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('deletes an application via DELETE with no body', async () => {
|
|
119
|
+
request.mockReturnValue(of({deleted: true}))
|
|
120
|
+
|
|
121
|
+
const {data} = await deleteApplication(instance, {applicationId: 'app1'})
|
|
122
|
+
|
|
123
|
+
expect(data).toEqual({deleted: true})
|
|
124
|
+
expect(request).toHaveBeenCalledWith({
|
|
125
|
+
uri: '/applications/app1',
|
|
126
|
+
method: 'DELETE',
|
|
127
|
+
tag: 'applications.delete',
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
})
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import {switchMap} from 'rxjs'
|
|
2
|
+
|
|
3
|
+
import {getClientState} from '../client/clientStore'
|
|
4
|
+
import {defineFetcher, defineMutation} from '../store/fetcherStore'
|
|
5
|
+
import {buildQuery} from '../utils/buildQuery'
|
|
6
|
+
import {type Included, type IncludesAny} from '../utils/includeShape'
|
|
7
|
+
|
|
8
|
+
// The Applications API is currently only available on vX.
|
|
9
|
+
const API_VERSION = 'vX'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Related data the Applications API can embed in a response. `interfaces`,
|
|
13
|
+
* `workspaces` and `access` are children of the active deployment and are
|
|
14
|
+
* embedded under {@link Application.activeDeployment}; `config.studio` and
|
|
15
|
+
* `config.mfManifest` are embedded under {@link Application.config}.
|
|
16
|
+
*
|
|
17
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export type ApplicationInclude =
|
|
21
|
+
| 'interfaces'
|
|
22
|
+
| 'workspaces'
|
|
23
|
+
| 'access'
|
|
24
|
+
| 'config.studio'
|
|
25
|
+
| 'config.mfManifest'
|
|
26
|
+
| 'activeDeployment'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* An interface exposed by a deployment, embedded when `interfaces` is included.
|
|
30
|
+
*
|
|
31
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export interface ApplicationInterface {
|
|
35
|
+
id: string
|
|
36
|
+
type: 'app' | 'worker' | 'asset_source' | 'panel'
|
|
37
|
+
name: string
|
|
38
|
+
title: string
|
|
39
|
+
version: string
|
|
40
|
+
/** Module federation module ID; resolved from the host mf-manifest at runtime */
|
|
41
|
+
moduleId: string
|
|
42
|
+
metadata: {group?: string; priority?: number} | null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A studio workspace of a deployment, embedded when `workspaces` is included.
|
|
47
|
+
* Studio applications only.
|
|
48
|
+
*
|
|
49
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
export interface ApplicationWorkspace {
|
|
53
|
+
id: string
|
|
54
|
+
name: string
|
|
55
|
+
title: string | null
|
|
56
|
+
subtitle: string | null
|
|
57
|
+
projectId: string
|
|
58
|
+
dataset: string
|
|
59
|
+
schemaDescriptorId: string | null
|
|
60
|
+
basePath: string | null
|
|
61
|
+
/** Sanitized SVG icon markup */
|
|
62
|
+
icon: string | null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A resource an application accesses, embedded when `access` is included.
|
|
67
|
+
*
|
|
68
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
69
|
+
* @public
|
|
70
|
+
*/
|
|
71
|
+
export interface ApplicationAccess {
|
|
72
|
+
id: string
|
|
73
|
+
resourceType: 'datasets' | 'canvases' | 'dashboards' | 'media-libraries'
|
|
74
|
+
resourceId: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Studio configuration, embedded under `config` when `config.studio` is included.
|
|
79
|
+
*
|
|
80
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
export interface ApplicationStudioConfig {
|
|
84
|
+
projectId: string
|
|
85
|
+
/** Release channel (`next`, `stable`, `latest`) or a pinned semver; null if unset */
|
|
86
|
+
autoUpdatingVersion: string | null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The active deployment as embedded under {@link Application.activeDeployment}.
|
|
91
|
+
* Its `interfaces`, `workspaces` and `access` children are each present only
|
|
92
|
+
* when the matching {@link ApplicationInclude} token was requested. `workspaces`
|
|
93
|
+
* is omitted (not `null`) for non-studio applications even when requested.
|
|
94
|
+
*
|
|
95
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
98
|
+
export type ApplicationDeployment<Include extends ApplicationInclude = never> = {
|
|
99
|
+
id: string
|
|
100
|
+
applicationId: string
|
|
101
|
+
size: number | null
|
|
102
|
+
version: string | null
|
|
103
|
+
isAutoUpdating: boolean | null
|
|
104
|
+
isActiveDeployment: boolean
|
|
105
|
+
deployedBy: string | null
|
|
106
|
+
createdAt: string
|
|
107
|
+
updatedAt: string
|
|
108
|
+
} & Included<ApplicationInclude, 'interfaces', Include, {interfaces: ApplicationInterface[]}> &
|
|
109
|
+
Included<ApplicationInclude, 'workspaces', Include, {workspaces?: ApplicationWorkspace[]}> &
|
|
110
|
+
Included<ApplicationInclude, 'access', Include, {access: ApplicationAccess[]}>
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The always-present fields of an org-owned application returned by the
|
|
114
|
+
* Applications API (`/applications`), before any {@link ApplicationInclude}.
|
|
115
|
+
*
|
|
116
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
117
|
+
* @public
|
|
118
|
+
*/
|
|
119
|
+
export interface ApplicationBase {
|
|
120
|
+
id: string
|
|
121
|
+
type: 'studio' | 'coreApp'
|
|
122
|
+
title: string
|
|
123
|
+
icon: string | null
|
|
124
|
+
isSingleton: boolean
|
|
125
|
+
visibility: 'default' | 'unlisted' | 'disabled'
|
|
126
|
+
slug: string | null
|
|
127
|
+
externalUrl: string | null
|
|
128
|
+
organizationId: string
|
|
129
|
+
createdAt: string
|
|
130
|
+
updatedAt: string
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Requesting any of these tokens forces the deployment summary into the response.
|
|
134
|
+
type DeploymentToken = 'activeDeployment' | 'interfaces' | 'workspaces' | 'access'
|
|
135
|
+
|
|
136
|
+
// `config` is present when either config child was requested; each child is
|
|
137
|
+
// gated on its own token.
|
|
138
|
+
type ApplicationConfigShape<Include extends ApplicationInclude> = Included<
|
|
139
|
+
ApplicationInclude,
|
|
140
|
+
'config.studio',
|
|
141
|
+
Include,
|
|
142
|
+
{config: {studio: ApplicationStudioConfig}}
|
|
143
|
+
> &
|
|
144
|
+
Included<ApplicationInclude, 'config.mfManifest', Include, {config: {mfManifest: unknown}}>
|
|
145
|
+
|
|
146
|
+
// `activeDeployment` is present when any deployment-related token was requested;
|
|
147
|
+
// a wide include array makes it optional.
|
|
148
|
+
type ApplicationDeploymentShape<Include extends ApplicationInclude> = [ApplicationInclude] extends [
|
|
149
|
+
Include,
|
|
150
|
+
]
|
|
151
|
+
? {activeDeployment?: ApplicationDeployment<Include> | null}
|
|
152
|
+
: IncludesAny<Include, DeploymentToken> extends true
|
|
153
|
+
? {activeDeployment: ApplicationDeployment<Include> | null}
|
|
154
|
+
: unknown
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* An org-owned application as returned by the Applications API
|
|
158
|
+
* (`/applications`). `config` and `activeDeployment` (with its
|
|
159
|
+
* `interfaces`/`workspaces`/`access` children) are present only when the
|
|
160
|
+
* corresponding {@link ApplicationInclude} tokens were requested.
|
|
161
|
+
*
|
|
162
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
163
|
+
* @public
|
|
164
|
+
*/
|
|
165
|
+
export type Application<Include extends ApplicationInclude = never> = ApplicationBase &
|
|
166
|
+
ApplicationConfigShape<Include> &
|
|
167
|
+
ApplicationDeploymentShape<Include>
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Options for listing an organization's applications.
|
|
171
|
+
*
|
|
172
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
173
|
+
* @public
|
|
174
|
+
*/
|
|
175
|
+
export interface ApplicationsOptions<Include extends ApplicationInclude = ApplicationInclude> {
|
|
176
|
+
organizationId: string
|
|
177
|
+
/** Filter by application type */
|
|
178
|
+
type?: string
|
|
179
|
+
include?: Include[]
|
|
180
|
+
/** Page size (1-100, default 50), or `'none'` to disable pagination */
|
|
181
|
+
limit?: number | 'none'
|
|
182
|
+
/** Cursor from a previous response's `nextCursor` */
|
|
183
|
+
cursor?: string
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The cursor-paginated envelope returned by `GET /applications`.
|
|
188
|
+
*
|
|
189
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
190
|
+
* @public
|
|
191
|
+
*/
|
|
192
|
+
export interface ApplicationsResponse<Include extends ApplicationInclude = never> {
|
|
193
|
+
nextCursor: string | null
|
|
194
|
+
data: Application<Include>[]
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** The API takes `include` as a comma-separated list; sorted so key and query agree */
|
|
198
|
+
function serializeInclude(include: ApplicationInclude[] | undefined): string | undefined {
|
|
199
|
+
return include?.length ? [...include].sort().join(',') : undefined
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Fetcher for an organization's applications (`GET /applications`), on the
|
|
204
|
+
* shared fetcher cache.
|
|
205
|
+
*
|
|
206
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
207
|
+
* @internal
|
|
208
|
+
*/
|
|
209
|
+
export const applications = defineFetcher<
|
|
210
|
+
[options: ApplicationsOptions],
|
|
211
|
+
ApplicationsResponse<ApplicationInclude>
|
|
212
|
+
>({
|
|
213
|
+
name: 'applications',
|
|
214
|
+
getKey: (_instance, options) =>
|
|
215
|
+
[
|
|
216
|
+
options.organizationId,
|
|
217
|
+
options.type ?? '',
|
|
218
|
+
serializeInclude(options.include) ?? '',
|
|
219
|
+
options.limit ?? '',
|
|
220
|
+
options.cursor ?? '',
|
|
221
|
+
].join(':'),
|
|
222
|
+
fetch: (instance) => (options) =>
|
|
223
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
224
|
+
switchMap((client) =>
|
|
225
|
+
client.observable.request<ApplicationsResponse<ApplicationInclude>>({
|
|
226
|
+
uri: '/applications',
|
|
227
|
+
query: buildQuery({
|
|
228
|
+
organizationId: options.organizationId,
|
|
229
|
+
type: options.type,
|
|
230
|
+
include: serializeInclude(options.include),
|
|
231
|
+
limit: options.limit,
|
|
232
|
+
cursor: options.cursor,
|
|
233
|
+
}),
|
|
234
|
+
tag: 'applications.list',
|
|
235
|
+
}),
|
|
236
|
+
),
|
|
237
|
+
),
|
|
238
|
+
tags: (data) => [
|
|
239
|
+
{type: 'application', id: 'LIST'},
|
|
240
|
+
...data.data.map((app) => ({type: 'application', id: app.id})),
|
|
241
|
+
],
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Fetcher for a single application (`GET /applications/:id`), on the shared
|
|
246
|
+
* fetcher cache. No pre-seeding from {@link applications}: its list keys
|
|
247
|
+
* include the organization, which this fetcher's params can't reconstruct.
|
|
248
|
+
*
|
|
249
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
250
|
+
* @internal
|
|
251
|
+
*/
|
|
252
|
+
export const application = defineFetcher<
|
|
253
|
+
[applicationId: string, options?: {include?: ApplicationInclude[]}],
|
|
254
|
+
Application<ApplicationInclude>
|
|
255
|
+
>({
|
|
256
|
+
name: 'application',
|
|
257
|
+
getKey: (_instance, applicationId, options) =>
|
|
258
|
+
`${applicationId}:${serializeInclude(options?.include) ?? ''}`,
|
|
259
|
+
fetch: (instance) => (applicationId, options) =>
|
|
260
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
261
|
+
switchMap((client) =>
|
|
262
|
+
client.observable.request<Application<ApplicationInclude>>({
|
|
263
|
+
uri: `/applications/${applicationId}`,
|
|
264
|
+
query: buildQuery({include: serializeInclude(options?.include)}),
|
|
265
|
+
tag: 'applications.get',
|
|
266
|
+
}),
|
|
267
|
+
),
|
|
268
|
+
),
|
|
269
|
+
tags: (data) => [{type: 'application', id: data.id}],
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Confirmation returned by the API's soft-delete endpoints.
|
|
274
|
+
*
|
|
275
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
276
|
+
* @internal
|
|
277
|
+
*/
|
|
278
|
+
export interface DeletedResult {
|
|
279
|
+
deleted: boolean
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Dashboard visibility for an application. The same set of values backs a user
|
|
284
|
+
* application's `dashboardStatus`.
|
|
285
|
+
*
|
|
286
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
287
|
+
* @internal
|
|
288
|
+
*/
|
|
289
|
+
export type ApplicationVisibility = ApplicationBase['visibility']
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Mutable properties of an application (`PATCH /applications/:id`) — the
|
|
293
|
+
* writable subset of {@link ApplicationBase}, each optional, keyed by id.
|
|
294
|
+
*
|
|
295
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
296
|
+
* @internal
|
|
297
|
+
*/
|
|
298
|
+
export type UpdateApplicationInput = {applicationId: string} & Partial<
|
|
299
|
+
Pick<ApplicationBase, 'title' | 'icon' | 'visibility'>
|
|
300
|
+
>
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Mutation to update an application (`PATCH /applications/:id`). Invalidates the
|
|
304
|
+
* updated application and the list so active entries reconverge on server truth.
|
|
305
|
+
*
|
|
306
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
307
|
+
* @internal
|
|
308
|
+
*/
|
|
309
|
+
export const updateApplication = defineMutation<UpdateApplicationInput, Application>({
|
|
310
|
+
name: 'updateApplication',
|
|
311
|
+
mutationFn:
|
|
312
|
+
(instance) =>
|
|
313
|
+
({applicationId, ...body}) =>
|
|
314
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
315
|
+
switchMap((client) =>
|
|
316
|
+
client.observable.request<Application>({
|
|
317
|
+
uri: `/applications/${applicationId}`,
|
|
318
|
+
method: 'PATCH',
|
|
319
|
+
body,
|
|
320
|
+
tag: 'applications.update',
|
|
321
|
+
}),
|
|
322
|
+
),
|
|
323
|
+
),
|
|
324
|
+
invalidates: (_result, {applicationId}) => [
|
|
325
|
+
{type: 'application', id: 'LIST'},
|
|
326
|
+
{type: 'application', id: applicationId},
|
|
327
|
+
],
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Input for {@link deleteApplication}.
|
|
332
|
+
*
|
|
333
|
+
* @internal
|
|
334
|
+
*/
|
|
335
|
+
export interface DeleteApplicationInput {
|
|
336
|
+
applicationId: string
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Mutation to soft-delete an application (`DELETE /applications/:id`).
|
|
341
|
+
* Invalidates the deleted application and the list.
|
|
342
|
+
*
|
|
343
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
344
|
+
* @internal
|
|
345
|
+
*/
|
|
346
|
+
export const deleteApplication = defineMutation<DeleteApplicationInput, DeletedResult>({
|
|
347
|
+
name: 'deleteApplication',
|
|
348
|
+
mutationFn:
|
|
349
|
+
(instance) =>
|
|
350
|
+
({applicationId}) =>
|
|
351
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
352
|
+
switchMap((client) =>
|
|
353
|
+
client.observable.request<DeletedResult>({
|
|
354
|
+
uri: `/applications/${applicationId}`,
|
|
355
|
+
method: 'DELETE',
|
|
356
|
+
tag: 'applications.delete',
|
|
357
|
+
}),
|
|
358
|
+
),
|
|
359
|
+
),
|
|
360
|
+
invalidates: (_result, {applicationId}) => [
|
|
361
|
+
{type: 'application', id: 'LIST'},
|
|
362
|
+
{type: 'application', id: applicationId},
|
|
363
|
+
],
|
|
364
|
+
})
|