@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,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
- getOrganizationState,
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('resolveOrganization — default call: members and features both omitted by default', () => {
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
- resolveOrganization(instance, {organizationId: 'org_1', includeMembers}),
61
- ).resolves.toEqualTypeOf<Organization<boolean, false>>()
12
+ organization.resolveState(instance, {organizationId: 'org_1'}),
13
+ ).resolves.toEqualTypeOf<Organization<boolean, boolean>>()
62
14
  })
63
15
 
64
- test('getOrganizationState — default call returns bare-base StateSource', () => {
65
- expectTypeOf(getOrganizationState(instance, {organizationId: 'org_1'})).toEqualTypeOf<
66
- StateSource<Organization<false, false> | undefined>
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('getOrganizationState — both flags true narrows the StateSource value type', () => {
71
- expectTypeOf(
72
- getOrganizationState(instance, {
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('Organization — wide boolean for IncludeFeatures makes features optional', () => {
90
- expectTypeOf<Organization<true, boolean>>().toEqualTypeOf<
91
- OrganizationBase & {members: OrganizationMember[]} & {features?: string[]}
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('Organization — both wide booleans make both fields optional', () => {
99
- expectTypeOf<Organization<boolean, boolean>>().toEqualTypeOf<
100
- OrganizationBase & {members?: OrganizationMember[]} & {features?: string[]}
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, resolveOrganization} from './organization'
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 organization = {id: 'org_1'}
25
- const request = vi.fn().mockReturnValue(of(organization))
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 resolveOrganization(instance, {organizationId: 'org_1'})
36
- expect(result).toEqual(organization)
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: 'organization.get',
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 resolveOrganization(instance, {
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: 'organization.get',
66
+ tag: 'organizations.get',
67
67
  })
68
68
  })
69
69
 
70
70
  it('throws when no organizationId is provided', async () => {
71
- await expect(resolveOrganization(instance, {organizationId: ''} as never)).rejects.toThrow(
72
- 'An organizationId is required to use the organization API.',
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 {type StateSource} from '../store/createStateSourceAction'
6
- import {createFetcherStore} from '../utils/createFetcherStore'
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
- * Public signature for the organization state source. The conditional generics
143
- * cannot flow through `BoundStoreAction`, so we declare the signature here
144
- * and assign the (already-correct) runtime function to it.
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
- type GetOrganizationState = <
147
- IncludeMembers extends boolean = false,
148
- IncludeFeatures extends boolean = false,
149
- >(
150
- instance: SanityInstance,
151
- options: OrganizationOptions<IncludeMembers, IncludeFeatures>,
152
- ) => StateSource<Organization<IncludeMembers, IncludeFeatures> | undefined>
153
-
154
- type ResolveOrganization = <
155
- IncludeMembers extends boolean = false,
156
- IncludeFeatures extends boolean = false,
157
- >(
158
- instance: SanityInstance,
159
- options: OrganizationOptions<IncludeMembers, IncludeFeatures>,
160
- ) => Promise<Organization<IncludeMembers, IncludeFeatures>>
161
-
162
- /** @public */
163
- export const getOrganizationState: GetOrganizationState = organization.getState
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 {getOrganizationsState, type Organizations, resolveOrganizations} from './organizations'
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('resolveOrganizations — default call: bare list shape', () => {
11
- expectTypeOf(resolveOrganizations(instance)).resolves.toEqualTypeOf<Organizations<false, false>>()
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('resolveOrganizations — includeFeatures: true narrows the generic', () => {
21
- expectTypeOf(resolveOrganizations(instance, {includeFeatures: true})).resolves.toEqualTypeOf<
22
- Organizations<false, true>
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('resolveOrganizations — both flags true', () => {
27
- expectTypeOf(
28
- resolveOrganizations(instance, {includeMembers: true, includeFeatures: true}),
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, resolveOrganizations} from './organizations'
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 organizations = [{id: 'org_1'}, {id: 'org_2'}]
25
- const request = vi.fn().mockReturnValue(of(organizations))
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 resolveOrganizations(instance)
36
- expect(result).toEqual(organizations)
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.get',
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 resolveOrganizations(instance, {
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.get',
71
+ tag: 'organizations.list',
72
72
  })
73
73
  })
74
74
  })