@sanity/sdk 2.10.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.
- package/dist/_chunks-dts/utils.d.ts +200 -28
- package/dist/_chunks-es/_internal.js +3 -14
- package/dist/_chunks-es/_internal.js.map +1 -1
- package/dist/_chunks-es/createGroqSearchFilter.js +7 -14
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -1
- package/dist/_chunks-es/version.js +1 -1
- package/dist/_exports/_internal.d.ts +16 -2
- package/dist/_exports/_internal.js +3 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +168 -88
- package/dist/index.js.map +1 -1
- package/package.json +7 -9
- package/src/_exports/_internal.ts +1 -0
- package/src/_exports/index.ts +25 -2
- package/src/agent/agentActions.ts +21 -25
- package/src/client/clientStore.test.ts +10 -46
- package/src/client/clientStore.ts +7 -14
- package/src/comlink/node/actions/getOrCreateNode.test.ts +5 -2
- package/src/comlink/node/actions/releaseNode.test.ts +3 -3
- package/src/config/sanityConfig.ts +0 -1
- package/src/document/documentStore.ts +2 -7
- package/src/document/sharedListener.ts +3 -5
- package/src/organization/organization.test-d.ts +102 -0
- package/src/organization/organization.test.ts +138 -0
- package/src/organization/organization.ts +166 -0
- package/src/organizations/organizations.test-d.ts +77 -0
- package/src/organizations/organizations.test.ts +150 -0
- package/src/organizations/organizations.ts +132 -0
- package/src/presence/presenceStore.test.ts +5 -5
- package/src/preview/previewProjectionUtils.ts +2 -3
- package/src/project/project.test-d.ts +93 -0
- package/src/project/project.test.ts +108 -10
- package/src/project/project.ts +152 -26
- package/src/projection/subscribeToStateAndFetchBatches.ts +4 -9
- package/src/projects/projects.test-d.ts +38 -0
- package/src/projects/projects.test.ts +104 -38
- package/src/projects/projects.ts +74 -14
- package/src/query/queryStore.ts +2 -3
- package/src/releases/releasesStore.test.ts +1 -1
- package/src/releases/releasesStore.ts +2 -2
- package/src/store/createSanityInstance.ts +3 -3
- package/src/telemetry/devMode.test.ts +8 -0
- package/src/telemetry/devMode.ts +10 -9
- package/src/telemetry/initTelemetry.test.ts +0 -17
- package/src/telemetry/initTelemetry.ts +2 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Sanity SDK",
|
|
6
6
|
"keywords": [
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@sanity/telemetry": "^1.1.0",
|
|
62
62
|
"@sanity/types": "^5.2.0",
|
|
63
63
|
"groq": "3.88.1-typegen-experimental.0",
|
|
64
|
-
"groq-js": "^1.
|
|
64
|
+
"groq-js": "^1.30.1",
|
|
65
65
|
"reselect": "^5.1.1",
|
|
66
66
|
"rxjs": "^7.8.2",
|
|
67
67
|
"zustand": "^5.0.12"
|
|
@@ -70,21 +70,19 @@
|
|
|
70
70
|
"@sanity/browserslist-config": "^1.0.5",
|
|
71
71
|
"@sanity/pkg-utils": "^8.1.29",
|
|
72
72
|
"@sanity/prettier-config": "^1.0.6",
|
|
73
|
-
"@
|
|
73
|
+
"@types/node": "^22.19.1",
|
|
74
|
+
"@vitest/coverage-v8": "4.1.5",
|
|
74
75
|
"eslint": "^9.22.0",
|
|
75
76
|
"prettier": "^3.7.3",
|
|
76
77
|
"rollup-plugin-visualizer": "^5.14.0",
|
|
77
78
|
"typescript": "^5.8.3",
|
|
78
79
|
"vite": "^7.0.0",
|
|
79
|
-
"vitest": "^
|
|
80
|
+
"vitest": "^4.1.4",
|
|
80
81
|
"@repo/config-eslint": "0.0.0",
|
|
81
|
-
"@repo/tsconfig": "0.0.1",
|
|
82
82
|
"@repo/package.bundle": "3.82.0",
|
|
83
|
+
"@repo/config-test": "0.0.1",
|
|
83
84
|
"@repo/package.config": "0.0.1",
|
|
84
|
-
"@repo/
|
|
85
|
-
},
|
|
86
|
-
"engines": {
|
|
87
|
-
"node": ">=20.19"
|
|
85
|
+
"@repo/tsconfig": "0.0.1"
|
|
88
86
|
},
|
|
89
87
|
"publishConfig": {
|
|
90
88
|
"access": "public"
|
|
@@ -12,3 +12,4 @@ export {getQueryKey, parseQueryKey} from '../query/queryStore' // only used for
|
|
|
12
12
|
export {getTelemetryManager, initTelemetry, trackHookMounted} from '../telemetry/initTelemetry'
|
|
13
13
|
export {getUsersKey, parseUsersKey} from '../users/reducers' // only used for memoizing in React, not needed for actual functionality
|
|
14
14
|
export {createGroqSearchFilter} from '../utils/createGroqSearchFilter'
|
|
15
|
+
export {isDeepEqual, pickProperties} from '../utils/object'
|
package/src/_exports/index.ts
CHANGED
|
@@ -157,6 +157,20 @@ export {type JsonMatch} from '../document/patchOperations'
|
|
|
157
157
|
export {type DocumentPermissionsResult, type PermissionDeniedReason} from '../document/permissions'
|
|
158
158
|
export type {FavoriteStatusResponse} from '../favorites/favorites'
|
|
159
159
|
export {getFavoritesState, resolveFavoritesState} from '../favorites/favorites'
|
|
160
|
+
export {
|
|
161
|
+
getOrganizationState,
|
|
162
|
+
type Organization,
|
|
163
|
+
type OrganizationBase,
|
|
164
|
+
type OrganizationMember,
|
|
165
|
+
type OrganizationOptions,
|
|
166
|
+
resolveOrganization,
|
|
167
|
+
} from '../organization/organization'
|
|
168
|
+
export {
|
|
169
|
+
getOrganizationsState,
|
|
170
|
+
type Organizations,
|
|
171
|
+
type OrganizationsOptions,
|
|
172
|
+
resolveOrganizations,
|
|
173
|
+
} from '../organizations/organizations'
|
|
160
174
|
export {getPresence} from '../presence/presenceStore'
|
|
161
175
|
export type {
|
|
162
176
|
DisconnectEvent,
|
|
@@ -178,11 +192,20 @@ export type {
|
|
|
178
192
|
ValuePending,
|
|
179
193
|
} from '../preview/types'
|
|
180
194
|
export {type OrgVerificationResult} from '../project/organizationVerification'
|
|
181
|
-
export {
|
|
195
|
+
export {
|
|
196
|
+
getProjectState,
|
|
197
|
+
type Project,
|
|
198
|
+
type ProjectBase,
|
|
199
|
+
type ProjectMember,
|
|
200
|
+
type ProjectMemberRole,
|
|
201
|
+
type ProjectMetadata,
|
|
202
|
+
type ProjectOptions,
|
|
203
|
+
resolveProject,
|
|
204
|
+
} from '../project/project'
|
|
182
205
|
export {getProjectionState} from '../projection/getProjectionState'
|
|
183
206
|
export {resolveProjection} from '../projection/resolveProjection'
|
|
184
207
|
export {type ProjectionValuePending, type ValidProjection} from '../projection/types'
|
|
185
|
-
export {getProjectsState, resolveProjects} from '../projects/projects'
|
|
208
|
+
export {getProjectsState, type ProjectsOptions, resolveProjects} from '../projects/projects'
|
|
186
209
|
export {
|
|
187
210
|
getQueryKey,
|
|
188
211
|
getQueryState,
|
|
@@ -2,6 +2,7 @@ import {type SanityClient} from '@sanity/client'
|
|
|
2
2
|
import {from, Observable, switchMap} from 'rxjs'
|
|
3
3
|
|
|
4
4
|
import {getClientState} from '../client/clientStore'
|
|
5
|
+
import {type DocumentResource} from '../config/sanityConfig'
|
|
5
6
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
6
7
|
|
|
7
8
|
const API_VERSION = 'vX'
|
|
@@ -58,12 +59,11 @@ export type AgentPatchResult = Awaited<ReturnType<SanityClient['agent']['action'
|
|
|
58
59
|
export function agentGenerate(
|
|
59
60
|
instance: SanityInstance,
|
|
60
61
|
options: AgentGenerateOptions,
|
|
62
|
+
resource?: DocumentResource,
|
|
61
63
|
): AgentGenerateResult {
|
|
62
|
-
return getClientState(instance, {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
dataset: instance.config.dataset,
|
|
66
|
-
}).observable.pipe(switchMap((client) => client.observable.agent.action.generate(options)))
|
|
64
|
+
return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
|
|
65
|
+
switchMap((client) => client.observable.agent.action.generate(options)),
|
|
66
|
+
)
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
@@ -76,12 +76,11 @@ export function agentGenerate(
|
|
|
76
76
|
export function agentTransform(
|
|
77
77
|
instance: SanityInstance,
|
|
78
78
|
options: AgentTransformOptions,
|
|
79
|
+
resource?: DocumentResource,
|
|
79
80
|
): AgentTransformResult {
|
|
80
|
-
return getClientState(instance, {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
dataset: instance.config.dataset,
|
|
84
|
-
}).observable.pipe(switchMap((client) => client.observable.agent.action.transform(options)))
|
|
81
|
+
return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
|
|
82
|
+
switchMap((client) => client.observable.agent.action.transform(options)),
|
|
83
|
+
)
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
/**
|
|
@@ -94,12 +93,11 @@ export function agentTransform(
|
|
|
94
93
|
export function agentTranslate(
|
|
95
94
|
instance: SanityInstance,
|
|
96
95
|
options: AgentTranslateOptions,
|
|
96
|
+
resource?: DocumentResource,
|
|
97
97
|
): AgentTranslateResult {
|
|
98
|
-
return getClientState(instance, {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
dataset: instance.config.dataset,
|
|
102
|
-
}).observable.pipe(switchMap((client) => client.observable.agent.action.translate(options)))
|
|
98
|
+
return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
|
|
99
|
+
switchMap((client) => client.observable.agent.action.translate(options)),
|
|
100
|
+
)
|
|
103
101
|
}
|
|
104
102
|
|
|
105
103
|
/**
|
|
@@ -112,12 +110,11 @@ export function agentTranslate(
|
|
|
112
110
|
export function agentPrompt(
|
|
113
111
|
instance: SanityInstance,
|
|
114
112
|
options: AgentPromptOptions,
|
|
113
|
+
resource?: DocumentResource,
|
|
115
114
|
): Observable<AgentPromptResult> {
|
|
116
|
-
return getClientState(instance, {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
dataset: instance.config.dataset,
|
|
120
|
-
}).observable.pipe(switchMap((client) => from(client.agent.action.prompt(options))))
|
|
115
|
+
return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
|
|
116
|
+
switchMap((client) => from(client.agent.action.prompt(options))),
|
|
117
|
+
)
|
|
121
118
|
}
|
|
122
119
|
|
|
123
120
|
/**
|
|
@@ -130,10 +127,9 @@ export function agentPrompt(
|
|
|
130
127
|
export function agentPatch(
|
|
131
128
|
instance: SanityInstance,
|
|
132
129
|
options: AgentPatchOptions,
|
|
130
|
+
resource?: DocumentResource,
|
|
133
131
|
): Observable<AgentPatchResult> {
|
|
134
|
-
return getClientState(instance, {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
dataset: instance.config.dataset,
|
|
138
|
-
}).observable.pipe(switchMap((client) => from(client.agent.action.patch(options))))
|
|
132
|
+
return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
|
|
133
|
+
switchMap((client) => from(client.agent.action.patch(options))),
|
|
134
|
+
)
|
|
139
135
|
}
|
|
@@ -191,29 +191,7 @@ describe('clientStore', () => {
|
|
|
191
191
|
})
|
|
192
192
|
|
|
193
193
|
describe('resource handling', () => {
|
|
194
|
-
it('should create
|
|
195
|
-
const client = getClient(instance, {
|
|
196
|
-
apiVersion: '2024-11-12',
|
|
197
|
-
resource: {projectId: 'source-project', dataset: 'source-dataset'},
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
expect(vi.mocked(createClient)).toHaveBeenCalledWith(
|
|
201
|
-
expect.objectContaining({
|
|
202
|
-
apiVersion: '2024-11-12',
|
|
203
|
-
resource: {type: 'dataset', id: 'source-project.source-dataset'},
|
|
204
|
-
}),
|
|
205
|
-
)
|
|
206
|
-
// Client should be projectless - no projectId/dataset in config
|
|
207
|
-
expect(client.config()).not.toHaveProperty('projectId')
|
|
208
|
-
expect(client.config()).not.toHaveProperty('dataset')
|
|
209
|
-
expect(client.config()).toEqual(
|
|
210
|
-
expect.objectContaining({
|
|
211
|
-
resource: {type: 'dataset', id: 'source-project.source-dataset'},
|
|
212
|
-
}),
|
|
213
|
-
)
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
it('should create resource when resource has array resourceId and be projectless', () => {
|
|
194
|
+
it('should create resource when media library resource is provided and be projectless', () => {
|
|
217
195
|
const client = getClient(instance, {
|
|
218
196
|
apiVersion: '2024-11-12',
|
|
219
197
|
resource: {mediaLibraryId: 'media-lib-123'},
|
|
@@ -257,38 +235,24 @@ describe('clientStore', () => {
|
|
|
257
235
|
)
|
|
258
236
|
})
|
|
259
237
|
|
|
260
|
-
it('should
|
|
238
|
+
it('should transform dataset resource to project-based config for now', () => {
|
|
261
239
|
const client = getClient(instance, {
|
|
262
240
|
apiVersion: '2024-11-12',
|
|
263
241
|
resource: {projectId: 'source-project', dataset: 'source-dataset'},
|
|
264
242
|
})
|
|
265
243
|
|
|
266
|
-
|
|
267
|
-
expect(client.config()).not.toHaveProperty('projectId')
|
|
268
|
-
expect(client.config()).not.toHaveProperty('dataset')
|
|
269
|
-
expect(client.config()).toEqual(
|
|
244
|
+
expect(vi.mocked(createClient)).toHaveBeenCalledWith(
|
|
270
245
|
expect.objectContaining({
|
|
271
|
-
|
|
246
|
+
projectId: 'source-project',
|
|
247
|
+
dataset: 'source-dataset',
|
|
272
248
|
}),
|
|
273
249
|
)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
apiVersion: '2024-11-12',
|
|
280
|
-
resource: {projectId: 'source-project', dataset: 'source-dataset'},
|
|
281
|
-
projectId: 'explicit-project',
|
|
282
|
-
dataset: 'explicit-dataset',
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
286
|
-
'Both resource and explicit projectId/dataset are provided. The resource will be used and projectId/dataset will be ignored.',
|
|
250
|
+
expect(client.config()).toEqual(
|
|
251
|
+
expect.objectContaining({
|
|
252
|
+
projectId: 'source-project',
|
|
253
|
+
dataset: 'source-dataset',
|
|
254
|
+
}),
|
|
287
255
|
)
|
|
288
|
-
// Client should still be projectless despite explicit projectId/dataset
|
|
289
|
-
expect(client.config()).not.toHaveProperty('projectId')
|
|
290
|
-
expect(client.config()).not.toHaveProperty('dataset')
|
|
291
|
-
consoleSpy.mockRestore()
|
|
292
256
|
})
|
|
293
257
|
|
|
294
258
|
it('should create different clients for different resources', () => {
|
|
@@ -178,24 +178,23 @@ export const getClient = bindActionGlobally(
|
|
|
178
178
|
|
|
179
179
|
const tokenFromState = state.get().token
|
|
180
180
|
const {clients, authMethod} = state.get()
|
|
181
|
+
let projectId = options.projectId ?? instance.config.projectId
|
|
182
|
+
let dataset = options.dataset ?? instance.config.dataset
|
|
181
183
|
|
|
182
184
|
let resource: ClientConfig['resource'] | undefined
|
|
183
185
|
|
|
184
186
|
if (options.resource) {
|
|
185
|
-
if (
|
|
186
|
-
resource = {
|
|
187
|
-
type: 'dataset',
|
|
188
|
-
id: `${options.resource.projectId}.${options.resource.dataset}`,
|
|
189
|
-
}
|
|
190
|
-
} else if (isMediaLibraryResource(options.resource)) {
|
|
187
|
+
if (isMediaLibraryResource(options.resource)) {
|
|
191
188
|
resource = {type: 'media-library', id: options.resource.mediaLibraryId}
|
|
192
189
|
} else if (isCanvasResource(options.resource)) {
|
|
193
190
|
resource = {type: 'canvas', id: options.resource.canvasId}
|
|
191
|
+
} else if (isDatasetResource(options.resource)) {
|
|
192
|
+
// use project-based routes for datasets to avoid existing CORS and Studio auth cookie issues
|
|
193
|
+
projectId = options.resource.projectId
|
|
194
|
+
dataset = options.resource.dataset
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
const projectId = options.projectId ?? instance.config.projectId
|
|
198
|
-
const dataset = options.dataset ?? instance.config.dataset
|
|
199
198
|
const apiHost = options.apiHost ?? instance.config.auth?.apiHost ?? getStagingApiHost()
|
|
200
199
|
|
|
201
200
|
const effectiveOptions: ClientConfig & {apiVersion: string} = {
|
|
@@ -213,12 +212,6 @@ export const getClient = bindActionGlobally(
|
|
|
213
212
|
// The client code itself will ignore the non-resource config, so we do this to prevent confusing the user.
|
|
214
213
|
// (ref: https://github.com/sanity-io/client/blob/5c23f81f5ab93a53f5b22b39845c867988508d84/src/data/dataMethods.ts#L691)
|
|
215
214
|
if (resource) {
|
|
216
|
-
if (options.projectId || options.dataset) {
|
|
217
|
-
// eslint-disable-next-line no-console
|
|
218
|
-
console.warn(
|
|
219
|
-
'Both resource and explicit projectId/dataset are provided. The resource will be used and projectId/dataset will be ignored.',
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
215
|
delete effectiveOptions.projectId
|
|
223
216
|
delete effectiveOptions.dataset
|
|
224
217
|
}
|
|
@@ -27,14 +27,17 @@ describe('getOrCreateNode', () => {
|
|
|
27
27
|
dataset: 'test-dataset',
|
|
28
28
|
})
|
|
29
29
|
let state: ReturnType<typeof createStoreState<ComlinkNodeState>>
|
|
30
|
-
let mockNode:
|
|
30
|
+
let mockNode: {
|
|
31
31
|
start: ReturnType<typeof vi.fn>
|
|
32
32
|
stop: ReturnType<typeof vi.fn>
|
|
33
|
+
onStatus: ReturnType<typeof vi.fn>
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
beforeEach(() => {
|
|
36
37
|
mockNode = {start: vi.fn(), stop: vi.fn(), onStatus: vi.fn()}
|
|
37
|
-
vi.mocked(comlink.createNode).mockReturnValue(
|
|
38
|
+
vi.mocked(comlink.createNode).mockReturnValue(
|
|
39
|
+
mockNode as unknown as Node<WindowMessage, FrameMessage>,
|
|
40
|
+
)
|
|
38
41
|
state = createStoreState<ComlinkNodeState>({nodes: new Map(), subscriptions: new Map()})
|
|
39
42
|
vi.clearAllMocks()
|
|
40
43
|
})
|
|
@@ -15,7 +15,7 @@ const nodeConfig = {
|
|
|
15
15
|
describe('releaseNode', () => {
|
|
16
16
|
let instance: SanityInstance
|
|
17
17
|
let state: ReturnType<typeof createStoreState<ComlinkNodeState>>
|
|
18
|
-
let mockNode:
|
|
18
|
+
let mockNode: {
|
|
19
19
|
start: ReturnType<typeof vi.fn>
|
|
20
20
|
stop: ReturnType<typeof vi.fn>
|
|
21
21
|
onStatus: ReturnType<typeof vi.fn>
|
|
@@ -39,7 +39,7 @@ describe('releaseNode', () => {
|
|
|
39
39
|
// Set up a node in the state
|
|
40
40
|
const nodes = new Map()
|
|
41
41
|
nodes.set('test-node', {
|
|
42
|
-
node: mockNode as Node<WindowMessage, FrameMessage>,
|
|
42
|
+
node: mockNode as unknown as Node<WindowMessage, FrameMessage>,
|
|
43
43
|
options: nodeConfig,
|
|
44
44
|
})
|
|
45
45
|
state.set('setup', {nodes})
|
|
@@ -58,7 +58,7 @@ describe('releaseNode', () => {
|
|
|
58
58
|
const statusUnsub = vi.fn()
|
|
59
59
|
const nodes = new Map()
|
|
60
60
|
nodes.set('test-node', {
|
|
61
|
-
node: mockNode as Node<WindowMessage, FrameMessage>,
|
|
61
|
+
node: mockNode as unknown as Node<WindowMessage, FrameMessage>,
|
|
62
62
|
options: nodeConfig,
|
|
63
63
|
statusUnsub,
|
|
64
64
|
})
|
|
@@ -125,7 +125,6 @@ export interface DocumentHandle<
|
|
|
125
125
|
export interface SanityConfig extends DatasetHandle, PerspectiveHandle {
|
|
126
126
|
/**
|
|
127
127
|
* Authentication configuration for the instance
|
|
128
|
-
* @remarks Merged with parent configurations when using createChild
|
|
129
128
|
*/
|
|
130
129
|
auth?: AuthConfig
|
|
131
130
|
/**
|
|
@@ -396,8 +396,7 @@ const subscribeToAppliedAndSubmitNextTransaction = ({
|
|
|
396
396
|
withLatestFrom(
|
|
397
397
|
getClientState(instance, {
|
|
398
398
|
apiVersion: API_VERSION,
|
|
399
|
-
|
|
400
|
-
resource: resource && !isDatasetResource(resource) ? resource : undefined,
|
|
399
|
+
resource,
|
|
401
400
|
}).observable,
|
|
402
401
|
),
|
|
403
402
|
concatMap(([outgoing, client]) => {
|
|
@@ -520,11 +519,7 @@ const subscribeToClientAndFetchDatasetAcl = ({
|
|
|
520
519
|
state,
|
|
521
520
|
key: {resource},
|
|
522
521
|
}: StoreContext<DocumentStoreState, BoundResourceKey>) => {
|
|
523
|
-
const clientOptions: ClientOptions = {apiVersion: API_VERSION}
|
|
524
|
-
// TODO: remove in v3 when we're ready for everything to be queried via resource
|
|
525
|
-
if (resource && !isDatasetResource(resource)) {
|
|
526
|
-
clientOptions.resource = resource
|
|
527
|
-
}
|
|
522
|
+
const clientOptions: ClientOptions = {apiVersion: API_VERSION, resource}
|
|
528
523
|
|
|
529
524
|
let uri: string
|
|
530
525
|
if (resource && isDatasetResource(resource)) {
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from 'rxjs'
|
|
15
15
|
|
|
16
16
|
import {getClientState} from '../client/clientStore'
|
|
17
|
-
import {type DocumentResource
|
|
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'
|
|
@@ -31,8 +31,7 @@ export function createSharedListener(
|
|
|
31
31
|
const dispose$ = new Subject<void>()
|
|
32
32
|
const events$ = getClientState(instance, {
|
|
33
33
|
apiVersion: API_VERSION,
|
|
34
|
-
|
|
35
|
-
resource: resource && !isDatasetResource(resource) ? resource : 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
|
|
@@ -72,8 +71,7 @@ export function createFetchDocument(instance: SanityInstance, resource?: Documen
|
|
|
72
71
|
return function (documentId: string): Observable<SanityDocument | null> {
|
|
73
72
|
return getClientState(instance, {
|
|
74
73
|
apiVersion: API_VERSION,
|
|
75
|
-
|
|
76
|
-
resource: resource && !isDatasetResource(resource) ? resource : 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
|
+
})
|