@sanity/sdk 2.18.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.
Files changed (53) hide show
  1. package/dist/_chunks-dts/createGroqSearchFilter.d.ts +233 -12
  2. package/dist/_chunks-dts/createGroqSearchFilter.d.ts.map +1 -1
  3. package/dist/_chunks-es/_internal.js +1 -0
  4. package/dist/_chunks-es/_internal.js.map +1 -1
  5. package/dist/_chunks-es/createGroqSearchFilter.js +284 -6
  6. package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -1
  7. package/dist/_chunks-es/telemetryManager.js +2 -2
  8. package/dist/_exports/_internal.d.ts +2 -2
  9. package/dist/_exports/_internal.d.ts.map +1 -1
  10. package/dist/_exports/_internal.js +3 -1
  11. package/dist/index.d.ts +599 -185
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +492 -337
  14. package/dist/index.js.map +1 -1
  15. package/package.json +7 -7
  16. package/src/_exports/_internal.ts +11 -0
  17. package/src/_exports/index.ts +57 -12
  18. package/src/access/checkPermissions.test.ts +55 -0
  19. package/src/access/checkPermissions.ts +54 -0
  20. package/src/applications/applications.test-d.ts +83 -0
  21. package/src/applications/applications.test.ts +130 -0
  22. package/src/applications/applications.ts +364 -0
  23. package/src/applications/userApplications.test.ts +126 -0
  24. package/src/applications/userApplications.ts +261 -0
  25. package/src/auth/getOrganizationVerificationState.test.ts +7 -7
  26. package/src/auth/getOrganizationVerificationState.ts +4 -4
  27. package/src/document/documentStore.concurrency.test.ts +313 -22
  28. package/src/installations/installations.test-d.ts +68 -0
  29. package/src/installations/installations.test.ts +62 -0
  30. package/src/installations/installations.ts +202 -0
  31. package/src/organization/organization.test-d.ts +18 -84
  32. package/src/organization/organization.test.ts +11 -11
  33. package/src/organization/organization.ts +26 -54
  34. package/src/organizations/organizations.test-d.ts +11 -32
  35. package/src/organizations/organizations.test.ts +8 -8
  36. package/src/organizations/organizations.ts +29 -54
  37. package/src/project/project.test-d.ts +19 -75
  38. package/src/project/project.test.ts +57 -10
  39. package/src/project/project.ts +33 -52
  40. package/src/projects/projects.test-d.ts +15 -21
  41. package/src/projects/projects.test.ts +8 -8
  42. package/src/projects/projects.ts +25 -49
  43. package/src/store/fetcherStore.test-d.ts +121 -0
  44. package/src/store/fetcherStore.test.ts +299 -0
  45. package/src/store/fetcherStore.ts +801 -0
  46. package/src/utils/buildQuery.test.ts +14 -0
  47. package/src/utils/buildQuery.ts +17 -0
  48. package/src/utils/includeShape.ts +36 -0
  49. package/src/utils/setCleanupTimeout.ts +21 -8
  50. package/dist/_chunks-es/version.js +0 -7
  51. package/dist/_chunks-es/version.js.map +0 -1
  52. package/src/utils/defineIntent.test.ts +0 -477
  53. package/src/utils/defineIntent.ts +0 -244
@@ -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
+ })
@@ -0,0 +1,126 @@
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 {
9
+ createUserApplication,
10
+ deleteUserApplication,
11
+ updateUserApplication,
12
+ userApplication,
13
+ userApplications,
14
+ } from './userApplications'
15
+
16
+ vi.mock('../client/clientStore')
17
+
18
+ describe('userApplications', () => {
19
+ let instance: SanityInstance
20
+ const request = vi.fn()
21
+
22
+ beforeEach(() => {
23
+ instance = createSanityInstance({projectId: 'p', dataset: 'd'})
24
+ request.mockReset()
25
+ vi.mocked(getClientState).mockReturnValue({
26
+ observable: of({observable: {request}} as unknown as SanityClient),
27
+ } as StateSource<SanityClient>)
28
+ })
29
+
30
+ afterEach(() => {
31
+ instance.dispose()
32
+ })
33
+
34
+ it('lists user applications for an organization on the global client', async () => {
35
+ const apps = [{id: 'ua1'}, {id: 'ua2'}]
36
+ request.mockReturnValue(of(apps))
37
+
38
+ const result = await userApplications.resolveState(instance, {organizationId: 'org1'})
39
+
40
+ expect(result).toEqual(apps)
41
+ expect(getClientState).toHaveBeenCalledWith(instance, {
42
+ apiVersion: 'v2024-08-01',
43
+ scope: 'global',
44
+ })
45
+ expect(request).toHaveBeenCalledWith({
46
+ uri: '/user-applications',
47
+ query: {organizationId: 'org1'},
48
+ tag: 'user-applications.list',
49
+ })
50
+ })
51
+
52
+ it('fetches a single user application by id', async () => {
53
+ const app = {id: 'ua1', appHost: 'my-studio'}
54
+ request.mockReturnValue(of(app))
55
+
56
+ const result = await userApplication.resolveState(instance, 'ua1')
57
+
58
+ expect(result).toEqual(app)
59
+ expect(request).toHaveBeenCalledWith({
60
+ uri: '/user-applications/ua1',
61
+ tag: 'user-applications.get',
62
+ })
63
+ })
64
+
65
+ it('creates a user application, sending query params and body, and refetches the list', async () => {
66
+ request.mockImplementation((opts) => {
67
+ const {method} = opts as {method?: string}
68
+ return method === 'POST' ? of({id: 'ua-new', title: 'New'}) : of([{id: 'ua-new'}])
69
+ })
70
+
71
+ const source = userApplications.getState(instance, {organizationId: 'org1'})
72
+ const unsubscribe = source.subscribe()
73
+ await vi.waitFor(() => expect(source.getCurrent().status).toBe('success'))
74
+
75
+ const {data, invalidated} = await createUserApplication(instance, {
76
+ organizationId: 'org1',
77
+ appType: 'studio',
78
+ urlType: 'internal',
79
+ appHost: 'my-studio',
80
+ title: 'New',
81
+ })
82
+ await invalidated
83
+
84
+ expect(data).toEqual({id: 'ua-new', title: 'New'})
85
+ expect(request).toHaveBeenCalledWith({
86
+ uri: '/user-applications',
87
+ method: 'POST',
88
+ query: {organizationId: 'org1', appType: 'studio'},
89
+ body: {urlType: 'internal', appHost: 'my-studio', title: 'New'},
90
+ tag: 'user-applications.create',
91
+ })
92
+ // initial list GET + POST + list refetch after invalidation
93
+ expect(request).toHaveBeenCalledTimes(3)
94
+ unsubscribe()
95
+ })
96
+
97
+ it('updates a user application via PATCH', async () => {
98
+ request.mockReturnValue(of({id: 'ua1', title: 'Renamed'}))
99
+
100
+ const {data} = await updateUserApplication(instance, {
101
+ userApplicationId: 'ua1',
102
+ title: 'Renamed',
103
+ })
104
+
105
+ expect(data).toEqual({id: 'ua1', title: 'Renamed'})
106
+ expect(request).toHaveBeenCalledWith({
107
+ uri: '/user-applications/ua1',
108
+ method: 'PATCH',
109
+ body: {title: 'Renamed'},
110
+ tag: 'user-applications.update',
111
+ })
112
+ })
113
+
114
+ it('deletes a user application via DELETE with no body', async () => {
115
+ request.mockReturnValue(of({deleted: true}))
116
+
117
+ const {data} = await deleteUserApplication(instance, {userApplicationId: 'ua1'})
118
+
119
+ expect(data).toEqual({deleted: true})
120
+ expect(request).toHaveBeenCalledWith({
121
+ uri: '/user-applications/ua1',
122
+ method: 'DELETE',
123
+ tag: 'user-applications.delete',
124
+ })
125
+ })
126
+ })