@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.
- package/dist/_chunks-dts/createGroqSearchFilter.d.ts +233 -12
- package/dist/_chunks-dts/createGroqSearchFilter.d.ts.map +1 -1
- package/dist/_chunks-es/_internal.js +1 -0
- package/dist/_chunks-es/_internal.js.map +1 -1
- package/dist/_chunks-es/createGroqSearchFilter.js +284 -6
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -1
- package/dist/_chunks-es/telemetryManager.js +2 -2
- package/dist/_exports/_internal.d.ts +2 -2
- package/dist/_exports/_internal.d.ts.map +1 -1
- package/dist/_exports/_internal.js +3 -1
- package/dist/index.d.ts +599 -185
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +492 -337
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/_exports/_internal.ts +11 -0
- package/src/_exports/index.ts +57 -12
- package/src/access/checkPermissions.test.ts +55 -0
- package/src/access/checkPermissions.ts +54 -0
- 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/getOrganizationVerificationState.test.ts +7 -7
- package/src/auth/getOrganizationVerificationState.ts +4 -4
- package/src/document/documentStore.concurrency.test.ts +313 -22
- 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 +18 -84
- package/src/organization/organization.test.ts +11 -11
- package/src/organization/organization.ts +26 -54
- package/src/organizations/organizations.test-d.ts +11 -32
- package/src/organizations/organizations.test.ts +8 -8
- package/src/organizations/organizations.ts +29 -54
- package/src/project/project.test-d.ts +19 -75
- package/src/project/project.test.ts +57 -10
- package/src/project/project.ts +33 -52
- package/src/projects/projects.test-d.ts +15 -21
- package/src/projects/projects.test.ts +8 -8
- package/src/projects/projects.ts +25 -49
- 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/utils/buildQuery.test.ts +14 -0
- package/src/utils/buildQuery.ts +17 -0
- package/src/utils/includeShape.ts +36 -0
- package/src/utils/setCleanupTimeout.ts +21 -8
- package/dist/_chunks-es/version.js +0 -7
- package/dist/_chunks-es/version.js.map +0 -1
- package/src/utils/defineIntent.test.ts +0 -477
- package/src/utils/defineIntent.ts +0 -244
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import {switchMap} from 'rxjs'
|
|
2
|
+
|
|
3
|
+
import {getClientState} from '../client/clientStore'
|
|
4
|
+
import {defineFetcher} from '../store/fetcherStore'
|
|
5
|
+
import {buildQuery} from '../utils/buildQuery'
|
|
6
|
+
import {type Included} 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 Installations API can embed in a response. Each token adds
|
|
13
|
+
* a top-level field to the {@link Installation} item.
|
|
14
|
+
*
|
|
15
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export type InstallationInclude = 'activeConfig' | 'access' | 'interfaces'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A resource an installation accesses, embedded when `access` is included.
|
|
22
|
+
*
|
|
23
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
export interface InstallationAccess {
|
|
27
|
+
id: string
|
|
28
|
+
resourceType: 'datasets' | 'canvases' | 'dashboards' | 'media-libraries'
|
|
29
|
+
resourceId: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* An interface exposed by an installation, embedded when `interfaces` is
|
|
34
|
+
* included.
|
|
35
|
+
*
|
|
36
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
export interface InstallationInterface {
|
|
40
|
+
id: string
|
|
41
|
+
type: 'app' | 'worker' | 'asset_source' | 'panel'
|
|
42
|
+
name: string
|
|
43
|
+
title: string
|
|
44
|
+
version: string
|
|
45
|
+
/** Module federation module ID; resolved from the host mf-manifest at runtime */
|
|
46
|
+
moduleId: string
|
|
47
|
+
metadata: {group?: string; priority?: number} | null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The active config of an installation, embedded when `activeConfig` is
|
|
52
|
+
* included. `null` when nothing is deployed.
|
|
53
|
+
*
|
|
54
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
export interface InstallationActiveConfig {
|
|
58
|
+
id: string
|
|
59
|
+
installationId: string
|
|
60
|
+
deployedBy: string | null
|
|
61
|
+
version: string
|
|
62
|
+
isActive: boolean
|
|
63
|
+
createdAt: string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The always-present fields of an active singleton installation returned by the
|
|
68
|
+
* Installations API (`/installations`), before any {@link InstallationInclude}.
|
|
69
|
+
*
|
|
70
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
71
|
+
* @public
|
|
72
|
+
*/
|
|
73
|
+
export interface InstallationBase {
|
|
74
|
+
id: string
|
|
75
|
+
applicationId: string
|
|
76
|
+
organizationId: string
|
|
77
|
+
installedBy: string | null
|
|
78
|
+
createdAt: string
|
|
79
|
+
updatedAt: string
|
|
80
|
+
application: {
|
|
81
|
+
title: string
|
|
82
|
+
slug: string | null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* An active singleton installation as returned by the Installations API
|
|
88
|
+
* (`/installations`). `activeConfig`, `access` and `interfaces` are top-level
|
|
89
|
+
* fields, each present only when the matching {@link InstallationInclude} token
|
|
90
|
+
* was requested.
|
|
91
|
+
*
|
|
92
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
93
|
+
* @public
|
|
94
|
+
*/
|
|
95
|
+
export type Installation<Include extends InstallationInclude = never> = InstallationBase &
|
|
96
|
+
Included<
|
|
97
|
+
InstallationInclude,
|
|
98
|
+
'activeConfig',
|
|
99
|
+
Include,
|
|
100
|
+
{activeConfig: InstallationActiveConfig | null}
|
|
101
|
+
> &
|
|
102
|
+
Included<InstallationInclude, 'access', Include, {access: InstallationAccess[]}> &
|
|
103
|
+
Included<InstallationInclude, 'interfaces', Include, {interfaces: InstallationInterface[]}>
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Options for listing an organization's installations.
|
|
107
|
+
*
|
|
108
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
109
|
+
* @public
|
|
110
|
+
*/
|
|
111
|
+
export interface InstallationsOptions<Include extends InstallationInclude = InstallationInclude> {
|
|
112
|
+
organizationId: string
|
|
113
|
+
include?: Include[]
|
|
114
|
+
/** Page size (1-100, default 50), or `'none'` to disable pagination */
|
|
115
|
+
limit?: number | 'none'
|
|
116
|
+
/** Cursor from a previous response's `nextCursor` */
|
|
117
|
+
cursor?: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The cursor-paginated envelope returned by `GET /installations`.
|
|
122
|
+
*
|
|
123
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
124
|
+
* @public
|
|
125
|
+
*/
|
|
126
|
+
export interface InstallationsResponse<Include extends InstallationInclude = never> {
|
|
127
|
+
nextCursor: string | null
|
|
128
|
+
data: Installation<Include>[]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The API takes `include` as a comma-separated list; sorted so key and query agree */
|
|
132
|
+
function serializeInclude(include: InstallationInclude[] | undefined): string | undefined {
|
|
133
|
+
return include?.length ? [...include].sort().join(',') : undefined
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Fetcher for an organization's installations (`GET /installations`), on the
|
|
138
|
+
* shared fetcher cache.
|
|
139
|
+
*
|
|
140
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
export const installations = defineFetcher<
|
|
144
|
+
[options: InstallationsOptions],
|
|
145
|
+
InstallationsResponse<InstallationInclude>
|
|
146
|
+
>({
|
|
147
|
+
name: 'installations',
|
|
148
|
+
getKey: (_instance, options) =>
|
|
149
|
+
[
|
|
150
|
+
options.organizationId,
|
|
151
|
+
serializeInclude(options.include) ?? '',
|
|
152
|
+
options.limit ?? '',
|
|
153
|
+
options.cursor ?? '',
|
|
154
|
+
].join(':'),
|
|
155
|
+
fetch: (instance) => (options) =>
|
|
156
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
157
|
+
switchMap((client) =>
|
|
158
|
+
client.observable.request<InstallationsResponse<InstallationInclude>>({
|
|
159
|
+
uri: '/installations',
|
|
160
|
+
query: buildQuery({
|
|
161
|
+
organizationId: options.organizationId,
|
|
162
|
+
include: serializeInclude(options.include),
|
|
163
|
+
limit: options.limit,
|
|
164
|
+
cursor: options.cursor,
|
|
165
|
+
}),
|
|
166
|
+
tag: 'installations.list',
|
|
167
|
+
}),
|
|
168
|
+
),
|
|
169
|
+
),
|
|
170
|
+
tags: (data) => [
|
|
171
|
+
{type: 'installation', id: 'LIST'},
|
|
172
|
+
...data.data.map((installation) => ({type: 'installation', id: installation.id})),
|
|
173
|
+
],
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Fetcher for a single installation (`GET /installations/:id`), on the shared
|
|
178
|
+
* fetcher cache. No pre-seeding from {@link installations}: its list keys
|
|
179
|
+
* include the organization, which this fetcher's params can't reconstruct.
|
|
180
|
+
*
|
|
181
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
182
|
+
* @internal
|
|
183
|
+
*/
|
|
184
|
+
export const installation = defineFetcher<
|
|
185
|
+
[installationId: string, options?: {include?: InstallationInclude[]}],
|
|
186
|
+
Installation<InstallationInclude>
|
|
187
|
+
>({
|
|
188
|
+
name: 'installation',
|
|
189
|
+
getKey: (_instance, installationId, options) =>
|
|
190
|
+
`${installationId}:${serializeInclude(options?.include) ?? ''}`,
|
|
191
|
+
fetch: (instance) => (installationId, options) =>
|
|
192
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
193
|
+
switchMap((client) =>
|
|
194
|
+
client.observable.request<Installation<InstallationInclude>>({
|
|
195
|
+
uri: `/installations/${installationId}`,
|
|
196
|
+
query: buildQuery({include: serializeInclude(options?.include)}),
|
|
197
|
+
tag: 'installations.get',
|
|
198
|
+
}),
|
|
199
|
+
),
|
|
200
|
+
),
|
|
201
|
+
tags: (data) => [{type: 'installation', id: data.id}],
|
|
202
|
+
})
|
|
@@ -2,101 +2,35 @@ import {expectTypeOf, test} from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
4
4
|
import {type StateSource} from '../store/createStateSourceAction'
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
type Organization,
|
|
8
|
-
type OrganizationBase,
|
|
9
|
-
type OrganizationMember,
|
|
10
|
-
resolveOrganization,
|
|
11
|
-
} from './organization'
|
|
5
|
+
import {type FetcherSnapshot} from '../store/fetcherStore'
|
|
6
|
+
import {type Organization, organization, type OrganizationMember} from './organization'
|
|
12
7
|
|
|
13
8
|
const instance = {} as SanityInstance
|
|
14
9
|
|
|
15
|
-
test('
|
|
16
|
-
expectTypeOf(resolveOrganization(instance, {organizationId: 'org_1'})).resolves.toEqualTypeOf<
|
|
17
|
-
Organization<false, false>
|
|
18
|
-
>()
|
|
19
|
-
type Result = Awaited<ReturnType<typeof resolveOrganization<false, false>>>
|
|
20
|
-
expectTypeOf<Result['id']>().toEqualTypeOf<string>()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test('resolveOrganization — includeMembers: true adds members to the type', () => {
|
|
24
|
-
expectTypeOf(
|
|
25
|
-
resolveOrganization(instance, {organizationId: 'org_1', includeMembers: true}),
|
|
26
|
-
).resolves.toEqualTypeOf<Organization<true, false>>()
|
|
27
|
-
type Result = Awaited<ReturnType<typeof resolveOrganization<true, false>>>
|
|
28
|
-
expectTypeOf<Result['members']>().toEqualTypeOf<OrganizationMember[]>()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
test('resolveOrganization — includeFeatures: true adds features to the type', () => {
|
|
32
|
-
expectTypeOf(
|
|
33
|
-
resolveOrganization(instance, {organizationId: 'org_1', includeFeatures: true}),
|
|
34
|
-
).resolves.toEqualTypeOf<Organization<false, true>>()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('resolveOrganization — both flags true → both arrays present', () => {
|
|
38
|
-
expectTypeOf(
|
|
39
|
-
resolveOrganization(instance, {
|
|
40
|
-
organizationId: 'org_1',
|
|
41
|
-
includeMembers: true,
|
|
42
|
-
includeFeatures: true,
|
|
43
|
-
}),
|
|
44
|
-
).resolves.toEqualTypeOf<Organization<true, true>>()
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('resolveOrganization — rejects non-boolean flag values', () => {
|
|
48
|
-
// @ts-expect-error — includeMembers must be a boolean
|
|
49
|
-
void resolveOrganization(instance, {organizationId: 'org_1', includeMembers: 'yes'})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test('resolveOrganization — requires organizationId', () => {
|
|
53
|
-
// @ts-expect-error — organizationId is required
|
|
54
|
-
void resolveOrganization(instance, {})
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
test('resolveOrganization — non-literal boolean flag makes members optional', () => {
|
|
58
|
-
const includeMembers = false as boolean
|
|
10
|
+
test('organization.resolveState — resolves the wide-boolean detail shape', () => {
|
|
59
11
|
expectTypeOf(
|
|
60
|
-
|
|
61
|
-
).resolves.toEqualTypeOf<Organization<boolean,
|
|
12
|
+
organization.resolveState(instance, {organizationId: 'org_1'}),
|
|
13
|
+
).resolves.toEqualTypeOf<Organization<boolean, boolean>>()
|
|
62
14
|
})
|
|
63
15
|
|
|
64
|
-
test('
|
|
65
|
-
expectTypeOf
|
|
66
|
-
|
|
67
|
-
>()
|
|
16
|
+
test('Organization — literal flags narrow members and features', () => {
|
|
17
|
+
expectTypeOf<Organization<true, true>['members']>().toEqualTypeOf<OrganizationMember[]>()
|
|
18
|
+
expectTypeOf<Organization<true, true>['features']>().toEqualTypeOf<string[]>()
|
|
19
|
+
expectTypeOf<keyof Organization<false, false> & ('members' | 'features')>().toEqualTypeOf<never>()
|
|
68
20
|
})
|
|
69
21
|
|
|
70
|
-
test('
|
|
71
|
-
expectTypeOf(
|
|
72
|
-
|
|
73
|
-
organizationId: 'org_1',
|
|
74
|
-
includeMembers: true,
|
|
75
|
-
includeFeatures: true,
|
|
76
|
-
}),
|
|
77
|
-
).toEqualTypeOf<StateSource<Organization<true, true> | undefined>>()
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
test('Organization — wide boolean for IncludeMembers makes members optional', () => {
|
|
81
|
-
expectTypeOf<Organization<boolean, true>>().toEqualTypeOf<
|
|
82
|
-
OrganizationBase & {members?: OrganizationMember[]} & {features: string[]}
|
|
22
|
+
test('organization.getState — exposes the snapshot envelope', () => {
|
|
23
|
+
expectTypeOf(organization.getState(instance, {organizationId: 'org_1'})).toEqualTypeOf<
|
|
24
|
+
StateSource<FetcherSnapshot<Organization<boolean, boolean>>>
|
|
83
25
|
>()
|
|
84
|
-
expectTypeOf<Pick<Organization<boolean, true>, 'members'>>().toEqualTypeOf<{
|
|
85
|
-
members?: OrganizationMember[]
|
|
86
|
-
}>()
|
|
87
26
|
})
|
|
88
27
|
|
|
89
|
-
test('
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
>()
|
|
93
|
-
expectTypeOf<Pick<Organization<true, boolean>, 'features'>>().toEqualTypeOf<{
|
|
94
|
-
features?: string[]
|
|
95
|
-
}>()
|
|
28
|
+
test('organization — requires organizationId in options', () => {
|
|
29
|
+
// @ts-expect-error organizationId is required
|
|
30
|
+
void organization.resolveState(instance, {})
|
|
96
31
|
})
|
|
97
32
|
|
|
98
|
-
test('
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
>()
|
|
33
|
+
test('organization — rejects non-boolean flag values', () => {
|
|
34
|
+
// @ts-expect-error includeMembers must be a boolean
|
|
35
|
+
void organization.resolveState(instance, {organizationId: 'org_1', includeMembers: 'yes'})
|
|
102
36
|
})
|
|
@@ -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 {getOrganizationCacheKey,
|
|
8
|
+
import {getOrganizationCacheKey, organization} from './organization'
|
|
9
9
|
|
|
10
10
|
vi.mock('../client/clientStore')
|
|
11
11
|
|
|
@@ -21,8 +21,8 @@ describe('organization', () => {
|
|
|
21
21
|
})
|
|
22
22
|
|
|
23
23
|
it('calls `client.observable.request` against `/organizations/<id>` and returns the result', async () => {
|
|
24
|
-
const
|
|
25
|
-
const request = vi.fn().mockReturnValue(of(
|
|
24
|
+
const detail = {id: 'org_1'}
|
|
25
|
+
const request = vi.fn().mockReturnValue(of(detail))
|
|
26
26
|
|
|
27
27
|
const mockClient = {
|
|
28
28
|
observable: {request} as unknown as SanityClient['observable'],
|
|
@@ -32,12 +32,12 @@ describe('organization', () => {
|
|
|
32
32
|
observable: of(mockClient),
|
|
33
33
|
} as StateSource<SanityClient>)
|
|
34
34
|
|
|
35
|
-
const result = await
|
|
36
|
-
expect(result).toEqual(
|
|
35
|
+
const result = await organization.resolveState(instance, {organizationId: 'org_1'})
|
|
36
|
+
expect(result).toEqual(detail)
|
|
37
37
|
expect(request).toHaveBeenCalledWith({
|
|
38
38
|
uri: '/organizations/org_1',
|
|
39
39
|
query: {includeMembers: 'false', includeFeatures: 'false'},
|
|
40
|
-
tag: '
|
|
40
|
+
tag: 'organizations.get',
|
|
41
41
|
})
|
|
42
42
|
})
|
|
43
43
|
|
|
@@ -51,7 +51,7 @@ describe('organization', () => {
|
|
|
51
51
|
observable: of(mockClient),
|
|
52
52
|
} as StateSource<SanityClient>)
|
|
53
53
|
|
|
54
|
-
await
|
|
54
|
+
await organization.resolveState(instance, {
|
|
55
55
|
organizationId: 'org_1',
|
|
56
56
|
includeMembers: true,
|
|
57
57
|
includeFeatures: true,
|
|
@@ -63,14 +63,14 @@ describe('organization', () => {
|
|
|
63
63
|
includeMembers: 'true',
|
|
64
64
|
includeFeatures: 'true',
|
|
65
65
|
},
|
|
66
|
-
tag: '
|
|
66
|
+
tag: 'organizations.get',
|
|
67
67
|
})
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
it('throws when no organizationId is provided', async () => {
|
|
71
|
-
await expect(
|
|
72
|
-
|
|
73
|
-
)
|
|
71
|
+
await expect(
|
|
72
|
+
organization.resolveState(instance, {organizationId: ''} as never),
|
|
73
|
+
).rejects.toThrow('An organizationId is required to use the organization API.')
|
|
74
74
|
})
|
|
75
75
|
})
|
|
76
76
|
|
|
@@ -2,8 +2,8 @@ import {switchMap} from 'rxjs'
|
|
|
2
2
|
|
|
3
3
|
import {getClientState} from '../client/clientStore'
|
|
4
4
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import {defineFetcher} from '../store/fetcherStore'
|
|
6
|
+
import {buildQuery} from '../utils/buildQuery'
|
|
7
7
|
|
|
8
8
|
const API_VERSION = 'v2025-02-19'
|
|
9
9
|
|
|
@@ -110,57 +110,29 @@ export function getOrganizationCacheKey(
|
|
|
110
110
|
return `organization:${organizationId}${membersKey}${featuresKey}`
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
const organization = createFetcherStore({
|
|
114
|
-
name: 'Organization',
|
|
115
|
-
getKey: getOrganizationCacheKey,
|
|
116
|
-
fetcher: (instance) => (options?: OrganizationOptions<boolean, boolean>) => {
|
|
117
|
-
const organizationId = resolveOrganizationId(options)
|
|
118
|
-
|
|
119
|
-
return getClientState(instance, {
|
|
120
|
-
apiVersion: API_VERSION,
|
|
121
|
-
scope: 'global',
|
|
122
|
-
}).observable.pipe(
|
|
123
|
-
switchMap((client) => {
|
|
124
|
-
const normalized = normalizeOrganizationOptions(options)
|
|
125
|
-
const query = Object.fromEntries(
|
|
126
|
-
Object.entries(normalized)
|
|
127
|
-
.filter(([, value]) => value !== undefined)
|
|
128
|
-
.map(([key, value]) => [key, String(value)]),
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
return client.observable.request({
|
|
132
|
-
uri: `/organizations/${organizationId}`,
|
|
133
|
-
query,
|
|
134
|
-
tag: 'organization.get',
|
|
135
|
-
})
|
|
136
|
-
}),
|
|
137
|
-
)
|
|
138
|
-
},
|
|
139
|
-
})
|
|
140
|
-
|
|
141
113
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
114
|
+
* Fetcher for a single organization (`GET /organizations/:id`), on the shared
|
|
115
|
+
* fetcher cache. No pre-seeding from the organizations list: it returns a
|
|
116
|
+
* subset of the detail fields, and a partial seed would serve as fresh for
|
|
117
|
+
* the whole stale window.
|
|
118
|
+
*
|
|
119
|
+
* @internal
|
|
145
120
|
*/
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
>(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
) =>
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
/** @public */
|
|
166
|
-
export const resolveOrganization: ResolveOrganization = organization.resolveState
|
|
121
|
+
export const organization = defineFetcher<
|
|
122
|
+
[options?: OrganizationOptions<boolean, boolean>],
|
|
123
|
+
Organization<boolean, boolean>
|
|
124
|
+
>({
|
|
125
|
+
name: 'organization',
|
|
126
|
+
getKey: getOrganizationCacheKey,
|
|
127
|
+
fetch: (instance) => (options) =>
|
|
128
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
129
|
+
switchMap((client) =>
|
|
130
|
+
client.observable.request<Organization<boolean, boolean>>({
|
|
131
|
+
uri: `/organizations/${resolveOrganizationId(options)}`,
|
|
132
|
+
query: buildQuery(normalizeOrganizationOptions(options)),
|
|
133
|
+
tag: 'organizations.get',
|
|
134
|
+
}),
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
tags: (data) => [{type: 'organization', id: data.id}],
|
|
138
|
+
})
|
|
@@ -3,41 +3,26 @@ import {expectTypeOf, test} from 'vitest'
|
|
|
3
3
|
import {type OrganizationMember} from '../organization/organization'
|
|
4
4
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
5
5
|
import {type StateSource} from '../store/createStateSourceAction'
|
|
6
|
-
import {
|
|
6
|
+
import {type FetcherSnapshot} from '../store/fetcherStore'
|
|
7
|
+
import {type Organizations, organizations} from './organizations'
|
|
7
8
|
|
|
8
9
|
const instance = {} as SanityInstance
|
|
9
10
|
|
|
10
|
-
test('
|
|
11
|
-
expectTypeOf(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
test('resolveOrganizations — includeMembers: true narrows the generic', () => {
|
|
15
|
-
expectTypeOf(resolveOrganizations(instance, {includeMembers: true})).resolves.toEqualTypeOf<
|
|
16
|
-
Organizations<true, false>
|
|
11
|
+
test('organizations.resolveState — resolves the wide-boolean list shape', () => {
|
|
12
|
+
expectTypeOf(organizations.resolveState(instance)).resolves.toEqualTypeOf<
|
|
13
|
+
Organizations<boolean, boolean>
|
|
17
14
|
>()
|
|
18
15
|
})
|
|
19
16
|
|
|
20
|
-
test('
|
|
21
|
-
expectTypeOf(
|
|
22
|
-
Organizations<
|
|
17
|
+
test('organizations.getState — exposes the snapshot envelope', () => {
|
|
18
|
+
expectTypeOf(organizations.getState(instance)).toEqualTypeOf<
|
|
19
|
+
StateSource<FetcherSnapshot<Organizations<boolean, boolean>>>
|
|
23
20
|
>()
|
|
24
21
|
})
|
|
25
22
|
|
|
26
|
-
test('
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
).resolves.toEqualTypeOf<Organizations<true, true>>()
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('resolveOrganizations — rejects non-boolean flag values', () => {
|
|
33
|
-
// @ts-expect-error — includeMembers must be a boolean
|
|
34
|
-
void resolveOrganizations(instance, {includeMembers: 'yes'})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('resolveOrganizations — includeImplicitMemberships does not change the data shape', () => {
|
|
38
|
-
expectTypeOf(
|
|
39
|
-
resolveOrganizations(instance, {includeImplicitMemberships: true}),
|
|
40
|
-
).resolves.toEqualTypeOf<Organizations<false, false>>()
|
|
23
|
+
test('organizations — rejects non-boolean flag values', () => {
|
|
24
|
+
// @ts-expect-error includeMembers must be a boolean
|
|
25
|
+
void organizations.resolveState(instance, {includeMembers: 'yes'})
|
|
41
26
|
})
|
|
42
27
|
|
|
43
28
|
test('Organizations — list items expose the documented subset of keys', () => {
|
|
@@ -69,9 +54,3 @@ test('Organizations<true, true>[number] exposes both members[] and features[]',
|
|
|
69
54
|
expectTypeOf<Item['members']>().toEqualTypeOf<OrganizationMember[]>()
|
|
70
55
|
expectTypeOf<Item['features']>().toEqualTypeOf<string[]>()
|
|
71
56
|
})
|
|
72
|
-
|
|
73
|
-
test('getOrganizationsState — default call returns the bare-base StateSource', () => {
|
|
74
|
-
expectTypeOf(getOrganizationsState(instance)).toEqualTypeOf<
|
|
75
|
-
StateSource<Organizations<false, false> | undefined>
|
|
76
|
-
>()
|
|
77
|
-
})
|
|
@@ -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 {getOrganizationsCacheKey,
|
|
8
|
+
import {getOrganizationsCacheKey, organizations} from './organizations'
|
|
9
9
|
|
|
10
10
|
vi.mock('../client/clientStore')
|
|
11
11
|
|
|
@@ -21,8 +21,8 @@ describe('organizations', () => {
|
|
|
21
21
|
})
|
|
22
22
|
|
|
23
23
|
it('calls `client.observable.request` against `/organizations` and returns the result', async () => {
|
|
24
|
-
const
|
|
25
|
-
const request = vi.fn().mockReturnValue(of(
|
|
24
|
+
const list = [{id: 'org_1'}, {id: 'org_2'}]
|
|
25
|
+
const request = vi.fn().mockReturnValue(of(list))
|
|
26
26
|
|
|
27
27
|
const mockClient = {
|
|
28
28
|
observable: {request} as unknown as SanityClient['observable'],
|
|
@@ -32,8 +32,8 @@ describe('organizations', () => {
|
|
|
32
32
|
observable: of(mockClient),
|
|
33
33
|
} as StateSource<SanityClient>)
|
|
34
34
|
|
|
35
|
-
const result = await
|
|
36
|
-
expect(result).toEqual(
|
|
35
|
+
const result = await organizations.resolveState(instance)
|
|
36
|
+
expect(result).toEqual(list)
|
|
37
37
|
expect(request).toHaveBeenCalledWith({
|
|
38
38
|
uri: '/organizations',
|
|
39
39
|
query: {
|
|
@@ -41,7 +41,7 @@ describe('organizations', () => {
|
|
|
41
41
|
includeMembers: 'false',
|
|
42
42
|
includeFeatures: 'false',
|
|
43
43
|
},
|
|
44
|
-
tag: 'organizations.
|
|
44
|
+
tag: 'organizations.list',
|
|
45
45
|
})
|
|
46
46
|
})
|
|
47
47
|
|
|
@@ -55,7 +55,7 @@ describe('organizations', () => {
|
|
|
55
55
|
observable: of(mockClient),
|
|
56
56
|
} as StateSource<SanityClient>)
|
|
57
57
|
|
|
58
|
-
await
|
|
58
|
+
await organizations.resolveState(instance, {
|
|
59
59
|
includeMembers: true,
|
|
60
60
|
includeFeatures: true,
|
|
61
61
|
includeImplicitMemberships: true,
|
|
@@ -68,7 +68,7 @@ describe('organizations', () => {
|
|
|
68
68
|
includeMembers: 'true',
|
|
69
69
|
includeFeatures: 'true',
|
|
70
70
|
},
|
|
71
|
-
tag: 'organizations.
|
|
71
|
+
tag: 'organizations.list',
|
|
72
72
|
})
|
|
73
73
|
})
|
|
74
74
|
})
|