@sanity/sdk 2.9.0 → 2.11.0

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 (73) hide show
  1. package/dist/_chunks-dts/utils.d.ts +295 -69
  2. package/dist/_chunks-es/_internal.js +3 -14
  3. package/dist/_chunks-es/_internal.js.map +1 -1
  4. package/dist/_chunks-es/createGroqSearchFilter.js +129 -59
  5. package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -1
  6. package/dist/_chunks-es/version.js +1 -1
  7. package/dist/_exports/_internal.d.ts +16 -2
  8. package/dist/_exports/_internal.js +3 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js +275 -149
  11. package/dist/index.js.map +1 -1
  12. package/package.json +11 -15
  13. package/src/_exports/_internal.ts +1 -0
  14. package/src/_exports/index.ts +33 -2
  15. package/src/agent/agentActions.ts +21 -25
  16. package/src/client/clientStore.test.ts +24 -60
  17. package/src/client/clientStore.ts +49 -56
  18. package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
  19. package/src/comlink/node/actions/getOrCreateNode.test.ts +5 -2
  20. package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
  21. package/src/comlink/node/actions/releaseNode.test.ts +3 -3
  22. package/src/config/sanityConfig.ts +72 -13
  23. package/src/document/applyDocumentActions.test.ts +7 -7
  24. package/src/document/applyDocumentActions.ts +5 -5
  25. package/src/document/documentStore.test.ts +68 -62
  26. package/src/document/documentStore.ts +33 -38
  27. package/src/document/processActions.ts +2 -2
  28. package/src/document/reducers.ts +4 -4
  29. package/src/document/sharedListener.ts +5 -7
  30. package/src/organization/organization.test-d.ts +102 -0
  31. package/src/organization/organization.test.ts +138 -0
  32. package/src/organization/organization.ts +166 -0
  33. package/src/organizations/organizations.test-d.ts +77 -0
  34. package/src/organizations/organizations.test.ts +150 -0
  35. package/src/organizations/organizations.ts +132 -0
  36. package/src/presence/bifurTransport.test.ts +46 -6
  37. package/src/presence/bifurTransport.ts +13 -1
  38. package/src/presence/presenceStore.test.ts +101 -5
  39. package/src/presence/presenceStore.ts +96 -24
  40. package/src/preview/getPreviewState.ts +1 -1
  41. package/src/preview/previewProjectionUtils.test.ts +4 -4
  42. package/src/preview/previewProjectionUtils.ts +6 -7
  43. package/src/preview/resolvePreview.ts +5 -1
  44. package/src/project/project.test-d.ts +93 -0
  45. package/src/project/project.test.ts +108 -10
  46. package/src/project/project.ts +152 -26
  47. package/src/projection/getProjectionState.ts +4 -4
  48. package/src/projection/projectionStore.test.ts +2 -2
  49. package/src/projection/resolveProjection.ts +2 -2
  50. package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
  51. package/src/projection/subscribeToStateAndFetchBatches.ts +11 -15
  52. package/src/projects/projects.test-d.ts +38 -0
  53. package/src/projects/projects.test.ts +104 -38
  54. package/src/projects/projects.ts +74 -14
  55. package/src/query/queryStore.test.ts +12 -12
  56. package/src/query/queryStore.ts +10 -11
  57. package/src/query/reducers.ts +3 -3
  58. package/src/releases/getPerspectiveState.ts +5 -5
  59. package/src/releases/releasesStore.test.ts +6 -6
  60. package/src/releases/releasesStore.ts +9 -9
  61. package/src/store/createActionBinder.test.ts +31 -31
  62. package/src/store/createActionBinder.ts +43 -38
  63. package/src/store/createSanityInstance.ts +5 -6
  64. package/src/telemetry/devMode.test.ts +8 -0
  65. package/src/telemetry/devMode.ts +10 -9
  66. package/src/telemetry/initTelemetry.test.ts +0 -17
  67. package/src/telemetry/initTelemetry.ts +2 -12
  68. package/src/users/reducers.ts +3 -4
  69. package/src/utils/createFetcherStore.ts +6 -4
  70. package/src/utils/isImportError.test.ts +72 -0
  71. package/src/utils/isImportError.ts +34 -0
  72. package/src/utils/object.test.ts +95 -0
  73. package/src/utils/object.ts +142 -0
@@ -1,11 +1,11 @@
1
1
  import {DocumentId, getDraftId, getPublishedId, getVersionId} from '@sanity/id-utils'
2
2
  import {type Mutation, type PatchOperations, type SanityDocumentLike} from '@sanity/types'
3
- import {omit} from 'lodash-es'
4
3
 
5
4
  import {type DocumentHandle} from '../config/sanityConfig'
6
5
  import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
7
6
  import {type StoreContext} from '../store/defineStore'
8
7
  import {insecureRandomId} from '../utils/ids'
8
+ import {omitProperty} from '../utils/object'
9
9
  import {setCleanupTimeout} from '../utils/setCleanupTimeout'
10
10
  import {type DocumentAction} from './actions'
11
11
  import {DOCUMENT_STATE_CLEAR_DELAY} from './documentConstants'
@@ -392,7 +392,7 @@ export function revertOutgoingTransaction(prev: SyncTransactionState): SyncTrans
392
392
  local: documentId in working ? working[documentId] : local,
393
393
  unverifiedRevisions:
394
394
  prev.outgoing && prev.outgoing.transactionId in unverifiedRevisions
395
- ? omit(unverifiedRevisions, prev.outgoing.transactionId)
395
+ ? omitProperty(unverifiedRevisions, prev.outgoing.transactionId)
396
396
  : unverifiedRevisions,
397
397
  }
398
398
  return [documentId, next] as const
@@ -420,7 +420,7 @@ export function applyRemoteDocument(
420
420
  const revisionToVerify = revision ? prevUnverifiedRevisions?.[revision] : undefined
421
421
  let unverifiedRevisions = prevUnverifiedRevisions ?? EMPTY_REVISIONS
422
422
  if (revision && revisionToVerify) {
423
- unverifiedRevisions = omit(prevUnverifiedRevisions, revision)
423
+ unverifiedRevisions = omitProperty(prevUnverifiedRevisions, revision)
424
424
  }
425
425
 
426
426
  // if this remote document is from a `'sync'` event (meaning that the whole
@@ -547,7 +547,7 @@ export function removeSubscriptionIdFromDocument(
547
547
 
548
548
  if (!prevDocState) return prev
549
549
  if (!subscriptions.length) {
550
- return {...prev, documentStates: omit(prev.documentStates, documentId)}
550
+ return {...prev, documentStates: omitProperty(prev.documentStates, documentId)}
551
551
  }
552
552
  return {
553
553
  ...prev,
@@ -14,7 +14,7 @@ import {
14
14
  } from 'rxjs'
15
15
 
16
16
  import {getClientState} from '../client/clientStore'
17
- import {type DocumentSource, isDatasetSource} from '../config/sanityConfig'
17
+ import {type DocumentResource} from '../config/sanityConfig'
18
18
  import {type SanityInstance} from '../store/createSanityInstance'
19
19
 
20
20
  const API_VERSION = 'v2025-05-06'
@@ -26,13 +26,12 @@ export interface SharedListener {
26
26
 
27
27
  export function createSharedListener(
28
28
  instance: SanityInstance,
29
- source?: DocumentSource,
29
+ resource?: DocumentResource,
30
30
  ): SharedListener {
31
31
  const dispose$ = new Subject<void>()
32
32
  const events$ = getClientState(instance, {
33
33
  apiVersion: API_VERSION,
34
- // TODO: remove in v3 when we're ready for everything to be queried via source
35
- source: source && !isDatasetSource(source) ? source : undefined,
34
+ resource,
36
35
  }).observable.pipe(
37
36
  switchMap((client) =>
38
37
  // TODO: it seems like the client.listen method is not emitting disconnected
@@ -68,12 +67,11 @@ export function createSharedListener(
68
67
  }
69
68
  }
70
69
 
71
- export function createFetchDocument(instance: SanityInstance, source?: DocumentSource) {
70
+ export function createFetchDocument(instance: SanityInstance, resource?: DocumentResource) {
72
71
  return function (documentId: string): Observable<SanityDocument | null> {
73
72
  return getClientState(instance, {
74
73
  apiVersion: API_VERSION,
75
- // TODO: remove in v3 when we're ready for everything to be queried via source
76
- source: source && !isDatasetSource(source) ? source : undefined,
74
+ resource,
77
75
  }).observable.pipe(
78
76
  switchMap((client) => {
79
77
  // creates a observable request to the /doc/{documentId} endpoint for a given document id
@@ -0,0 +1,102 @@
1
+ import {expectTypeOf, test} from 'vitest'
2
+
3
+ import {type SanityInstance} from '../store/createSanityInstance'
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'
12
+
13
+ const instance = {} as SanityInstance
14
+
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
59
+ expectTypeOf(
60
+ resolveOrganization(instance, {organizationId: 'org_1', includeMembers}),
61
+ ).resolves.toEqualTypeOf<Organization<boolean, false>>()
62
+ })
63
+
64
+ test('getOrganizationState — default call returns bare-base StateSource', () => {
65
+ expectTypeOf(getOrganizationState(instance, {organizationId: 'org_1'})).toEqualTypeOf<
66
+ StateSource<Organization<false, false> | undefined>
67
+ >()
68
+ })
69
+
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[]}
83
+ >()
84
+ expectTypeOf<Pick<Organization<boolean, true>, 'members'>>().toEqualTypeOf<{
85
+ members?: OrganizationMember[]
86
+ }>()
87
+ })
88
+
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
+ }>()
96
+ })
97
+
98
+ test('Organization — both wide booleans make both fields optional', () => {
99
+ expectTypeOf<Organization<boolean, boolean>>().toEqualTypeOf<
100
+ OrganizationBase & {members?: OrganizationMember[]} & {features?: string[]}
101
+ >()
102
+ })
@@ -0,0 +1,138 @@
1
+ import {type SanityClient} from '@sanity/client'
2
+ import {of} from 'rxjs'
3
+ import {afterEach, beforeEach, describe, it} 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 {getOrganizationCacheKey, resolveOrganization} from './organization'
9
+
10
+ vi.mock('../client/clientStore')
11
+
12
+ describe('organization', () => {
13
+ let instance: SanityInstance
14
+
15
+ beforeEach(() => {
16
+ instance = createSanityInstance({projectId: 'p', dataset: 'd'})
17
+ })
18
+
19
+ afterEach(() => {
20
+ instance.dispose()
21
+ })
22
+
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))
26
+
27
+ const mockClient = {
28
+ observable: {request} as unknown as SanityClient['observable'],
29
+ } as SanityClient
30
+
31
+ vi.mocked(getClientState).mockReturnValue({
32
+ observable: of(mockClient),
33
+ } as StateSource<SanityClient>)
34
+
35
+ const result = await resolveOrganization(instance, {organizationId: 'org_1'})
36
+ expect(result).toEqual(organization)
37
+ expect(request).toHaveBeenCalledWith({
38
+ uri: '/organizations/org_1',
39
+ query: {includeMembers: 'false', includeFeatures: 'false'},
40
+ tag: 'organization.get',
41
+ })
42
+ })
43
+
44
+ it('serializes query params (booleans → strings) and respects flags', async () => {
45
+ const request = vi.fn().mockReturnValue(of({id: 'org_1'}))
46
+ const mockClient = {
47
+ observable: {request} as unknown as SanityClient['observable'],
48
+ } as SanityClient
49
+
50
+ vi.mocked(getClientState).mockReturnValue({
51
+ observable: of(mockClient),
52
+ } as StateSource<SanityClient>)
53
+
54
+ await resolveOrganization(instance, {
55
+ organizationId: 'org_1',
56
+ includeMembers: true,
57
+ includeFeatures: true,
58
+ })
59
+
60
+ expect(request).toHaveBeenCalledWith({
61
+ uri: '/organizations/org_1',
62
+ query: {
63
+ includeMembers: 'true',
64
+ includeFeatures: 'true',
65
+ },
66
+ tag: 'organization.get',
67
+ })
68
+ })
69
+
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
+ )
74
+ })
75
+ })
76
+
77
+ describe('organization cache key generation', () => {
78
+ let instance: SanityInstance
79
+
80
+ beforeEach(() => {
81
+ instance = createSanityInstance({})
82
+ })
83
+
84
+ afterEach(() => {
85
+ instance.dispose()
86
+ })
87
+
88
+ it('default call excludes :members and :features (both default-false)', () => {
89
+ expect(getOrganizationCacheKey(instance, {organizationId: 'org_1'})).toBe('organization:org_1')
90
+ })
91
+
92
+ it('treats undefined and the matching default as the same key', () => {
93
+ expect(getOrganizationCacheKey(instance, {organizationId: 'org_1'})).toBe(
94
+ getOrganizationCacheKey(instance, {
95
+ organizationId: 'org_1',
96
+ includeMembers: false,
97
+ includeFeatures: false,
98
+ }),
99
+ )
100
+ })
101
+
102
+ it('explicit includeMembers: true appends :members', () => {
103
+ expect(getOrganizationCacheKey(instance, {organizationId: 'org_1', includeMembers: true})).toBe(
104
+ 'organization:org_1:members',
105
+ )
106
+ })
107
+
108
+ it('explicit includeFeatures: true appends :features', () => {
109
+ expect(
110
+ getOrganizationCacheKey(instance, {organizationId: 'org_1', includeFeatures: true}),
111
+ ).toBe('organization:org_1:features')
112
+ })
113
+
114
+ it('combines all segments in order', () => {
115
+ expect(
116
+ getOrganizationCacheKey(instance, {
117
+ organizationId: 'org_1',
118
+ includeMembers: true,
119
+ includeFeatures: true,
120
+ }),
121
+ ).toBe('organization:org_1:members:features')
122
+ })
123
+
124
+ it('produces distinct keys for each meaningful option permutation', () => {
125
+ const keys = new Set([
126
+ getOrganizationCacheKey(instance, {organizationId: 'org_1'}),
127
+ getOrganizationCacheKey(instance, {organizationId: 'org_1', includeMembers: true}),
128
+ getOrganizationCacheKey(instance, {organizationId: 'org_1', includeFeatures: true}),
129
+ getOrganizationCacheKey(instance, {
130
+ organizationId: 'org_1',
131
+ includeMembers: true,
132
+ includeFeatures: true,
133
+ }),
134
+ getOrganizationCacheKey(instance, {organizationId: 'org_2'}),
135
+ ])
136
+ expect(keys.size).toBe(5)
137
+ })
138
+ })
@@ -0,0 +1,166 @@
1
+ import {switchMap} from 'rxjs'
2
+
3
+ import {getClientState} from '../client/clientStore'
4
+ import {type SanityInstance} from '../store/createSanityInstance'
5
+ import {type StateSource} from '../store/createStateSourceAction'
6
+ import {createFetcherStore} from '../utils/createFetcherStore'
7
+
8
+ const API_VERSION = 'v2025-02-19'
9
+
10
+ /** @public */
11
+ export interface OrganizationMember {
12
+ sanityUserId: string
13
+ isCurrentUser: boolean
14
+ user: {
15
+ id: string
16
+ displayName: string
17
+ familyName: string
18
+ givenName: string
19
+ middleName: string | null
20
+ imageUrl: string | null
21
+ email: string
22
+ loginProvider: string
23
+ }
24
+ roles: Array<{
25
+ name: string
26
+ title: string
27
+ description?: string
28
+ }>
29
+ }
30
+
31
+ /**
32
+ * The base fields returned from `/organizations/<id>` for every organization.
33
+ * @public
34
+ */
35
+ export interface OrganizationBase {
36
+ id: string
37
+ name: string
38
+ slug: string | null
39
+ createdAt: string
40
+ createdByUserId: string
41
+ updatedAt: string
42
+ deletedAt: string | null
43
+ dashboardStatus: 'enabled' | 'disabled'
44
+ aiFeaturesStatus: 'enabled' | 'disabled'
45
+ mediaLibraryStatus: 'enabled' | 'disabled'
46
+ requestAccessStatus: 'allowed' | 'disabled'
47
+ telemetryConsentStatus: 'allowed' | 'msa_denied' | 'customer_denied'
48
+ oauthAppsStatus: 'allowed' | 'blocked'
49
+ defaultRoleName: string
50
+ domains: string[] | null
51
+ }
52
+
53
+ /** @public */
54
+ export interface OrganizationOptions<
55
+ IncludeMembers extends boolean = false,
56
+ IncludeFeatures extends boolean = false,
57
+ > {
58
+ includeMembers?: IncludeMembers
59
+ includeFeatures?: IncludeFeatures
60
+ organizationId: string
61
+ }
62
+
63
+ /**
64
+ * An `Organization` with `members` and/or `features` conditionally included
65
+ * based on the query options used to fetch it.
66
+ * @public
67
+ */
68
+ export type Organization<
69
+ IncludeMembers extends boolean = false,
70
+ IncludeFeatures extends boolean = false,
71
+ > = OrganizationBase &
72
+ // `boolean extends T` is non-distributive — true only when T is the wide
73
+ // `boolean`, in which case the field is optional. Literal `true`/`false`
74
+ // fall through to the strict branch.
75
+ (boolean extends IncludeMembers
76
+ ? {members?: OrganizationMember[]}
77
+ : IncludeMembers extends true
78
+ ? {members: OrganizationMember[]}
79
+ : unknown) &
80
+ (boolean extends IncludeFeatures
81
+ ? {features?: string[]}
82
+ : IncludeFeatures extends true
83
+ ? {features: string[]}
84
+ : unknown)
85
+
86
+ function resolveOrganizationId(options?: OrganizationOptions<boolean, boolean>) {
87
+ const organizationId = options?.organizationId
88
+ if (!organizationId) {
89
+ throw new Error('An organizationId is required to use the organization API.')
90
+ }
91
+ return organizationId
92
+ }
93
+
94
+ function normalizeOrganizationOptions(options?: OrganizationOptions<boolean, boolean>) {
95
+ return {
96
+ includeMembers: options?.includeMembers ?? false,
97
+ includeFeatures: options?.includeFeatures ?? false,
98
+ }
99
+ }
100
+
101
+ /** @internal */
102
+ export function getOrganizationCacheKey(
103
+ _instance: SanityInstance,
104
+ options?: OrganizationOptions<boolean, boolean>,
105
+ ): string {
106
+ const organizationId = resolveOrganizationId(options)
107
+ const {includeMembers, includeFeatures} = normalizeOrganizationOptions(options)
108
+ const membersKey = includeMembers ? ':members' : ''
109
+ const featuresKey = includeFeatures ? ':features' : ''
110
+ return `organization:${organizationId}${membersKey}${featuresKey}`
111
+ }
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
+ /**
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.
145
+ */
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
@@ -0,0 +1,77 @@
1
+ import {expectTypeOf, test} from 'vitest'
2
+
3
+ import {type OrganizationMember} from '../organization/organization'
4
+ import {type SanityInstance} from '../store/createSanityInstance'
5
+ import {type StateSource} from '../store/createStateSourceAction'
6
+ import {getOrganizationsState, type Organizations, resolveOrganizations} from './organizations'
7
+
8
+ const instance = {} as SanityInstance
9
+
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>
17
+ >()
18
+ })
19
+
20
+ test('resolveOrganizations — includeFeatures: true narrows the generic', () => {
21
+ expectTypeOf(resolveOrganizations(instance, {includeFeatures: true})).resolves.toEqualTypeOf<
22
+ Organizations<false, true>
23
+ >()
24
+ })
25
+
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>>()
41
+ })
42
+
43
+ test('Organizations — list items expose the documented subset of keys', () => {
44
+ type Keys = keyof Organizations<false, false>[number]
45
+ expectTypeOf<Keys>().toEqualTypeOf<
46
+ | 'id'
47
+ | 'name'
48
+ | 'slug'
49
+ | 'createdAt'
50
+ | 'updatedAt'
51
+ | 'defaultRoleName'
52
+ | 'dashboardStatus'
53
+ | 'aiFeaturesStatus'
54
+ >()
55
+ })
56
+
57
+ test('Organizations<true, false>[number] exposes members[]', () => {
58
+ type Item = Organizations<true, false>[number]
59
+ expectTypeOf<Item['members']>().toEqualTypeOf<OrganizationMember[]>()
60
+ })
61
+
62
+ test('Organizations<false, true>[number] exposes features[]', () => {
63
+ type Item = Organizations<false, true>[number]
64
+ expectTypeOf<Item['features']>().toEqualTypeOf<string[]>()
65
+ })
66
+
67
+ test('Organizations<true, true>[number] exposes both members[] and features[]', () => {
68
+ type Item = Organizations<true, true>[number]
69
+ expectTypeOf<Item['members']>().toEqualTypeOf<OrganizationMember[]>()
70
+ expectTypeOf<Item['features']>().toEqualTypeOf<string[]>()
71
+ })
72
+
73
+ test('getOrganizationsState — default call returns the bare-base StateSource', () => {
74
+ expectTypeOf(getOrganizationsState(instance)).toEqualTypeOf<
75
+ StateSource<Organizations<false, false> | undefined>
76
+ >()
77
+ })