@sanity/sdk 2.5.0 → 2.7.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/index.d.ts +429 -27
- package/dist/index.js +657 -266
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/_exports/index.ts +18 -3
- package/src/auth/authMode.test.ts +56 -0
- package/src/auth/authMode.ts +71 -0
- package/src/auth/authStore.test.ts +85 -4
- package/src/auth/authStore.ts +63 -125
- package/src/auth/authStrategy.ts +39 -0
- package/src/auth/dashboardAuth.ts +132 -0
- package/src/auth/standaloneAuth.ts +109 -0
- package/src/auth/studioAuth.ts +217 -0
- package/src/auth/studioModeAuth.test.ts +43 -1
- package/src/auth/studioModeAuth.ts +10 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +21 -6
- package/src/client/clientStore.test.ts +45 -43
- package/src/client/clientStore.ts +23 -9
- package/src/config/loggingConfig.ts +149 -0
- package/src/config/sanityConfig.ts +82 -22
- package/src/projection/getProjectionState.ts +6 -5
- package/src/projection/projectionQuery.test.ts +38 -55
- package/src/projection/projectionQuery.ts +27 -31
- package/src/projection/projectionStore.test.ts +4 -4
- package/src/projection/projectionStore.ts +3 -2
- package/src/projection/resolveProjection.ts +2 -2
- package/src/projection/statusQuery.test.ts +35 -0
- package/src/projection/statusQuery.ts +71 -0
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +63 -50
- package/src/projection/subscribeToStateAndFetchBatches.ts +106 -27
- package/src/projection/types.ts +12 -0
- package/src/projection/util.ts +0 -1
- package/src/query/queryStore.test.ts +64 -0
- package/src/query/queryStore.ts +33 -11
- package/src/releases/getPerspectiveState.test.ts +17 -14
- package/src/releases/getPerspectiveState.ts +58 -38
- package/src/releases/releasesStore.test.ts +59 -61
- package/src/releases/releasesStore.ts +21 -35
- package/src/releases/utils/isReleasePerspective.ts +7 -0
- package/src/store/createActionBinder.test.ts +211 -1
- package/src/store/createActionBinder.ts +102 -13
- package/src/store/createSanityInstance.test.ts +85 -1
- package/src/store/createSanityInstance.ts +55 -4
- package/src/utils/logger-usage-example.md +141 -0
- package/src/utils/logger.test.ts +757 -0
- package/src/utils/logger.ts +537 -0
|
@@ -3,7 +3,6 @@ import {Subject} from 'rxjs'
|
|
|
3
3
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
4
4
|
|
|
5
5
|
import {getAuthMethodState, getTokenState} from '../auth/authStore'
|
|
6
|
-
import {canvasSource, datasetSource, mediaLibrarySource} from '../config/sanityConfig'
|
|
7
6
|
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
8
7
|
import {getClient, getClientState} from './clientStore'
|
|
9
8
|
|
|
@@ -34,7 +33,10 @@ beforeEach(() => {
|
|
|
34
33
|
vi.mocked(createClient).mockImplementation(
|
|
35
34
|
(clientConfig) => ({config: () => clientConfig}) as SanityClient,
|
|
36
35
|
)
|
|
37
|
-
instance = createSanityInstance({
|
|
36
|
+
instance = createSanityInstance({
|
|
37
|
+
projectId: 'test-project',
|
|
38
|
+
dataset: 'test-dataset',
|
|
39
|
+
})
|
|
38
40
|
})
|
|
39
41
|
|
|
40
42
|
afterEach(() => {
|
|
@@ -176,18 +178,15 @@ describe('clientStore', () => {
|
|
|
176
178
|
|
|
177
179
|
describe('source handling', () => {
|
|
178
180
|
it('should create client when source is provided', () => {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
+
const client = getClient(instance, {
|
|
182
|
+
apiVersion: '2024-11-12',
|
|
183
|
+
source: {projectId: 'source-project', dataset: 'source-dataset'},
|
|
184
|
+
})
|
|
181
185
|
|
|
182
186
|
expect(vi.mocked(createClient)).toHaveBeenCalledWith(
|
|
183
187
|
expect.objectContaining({
|
|
184
|
-
apiVersion: '2024-11-12',
|
|
185
|
-
|
|
186
|
-
__sanity_internal_sourceId: {
|
|
187
|
-
projectId: 'source-project',
|
|
188
|
-
dataset: 'source-dataset',
|
|
189
|
-
},
|
|
190
|
-
}),
|
|
188
|
+
'apiVersion': '2024-11-12',
|
|
189
|
+
'~experimental_resource': {type: 'dataset', id: 'source-project.source-dataset'},
|
|
191
190
|
}),
|
|
192
191
|
)
|
|
193
192
|
// Client should be projectless - no projectId/dataset in config
|
|
@@ -195,19 +194,16 @@ describe('clientStore', () => {
|
|
|
195
194
|
expect(client.config()).not.toHaveProperty('dataset')
|
|
196
195
|
expect(client.config()).toEqual(
|
|
197
196
|
expect.objectContaining({
|
|
198
|
-
|
|
199
|
-
__sanity_internal_sourceId: {
|
|
200
|
-
projectId: 'source-project',
|
|
201
|
-
dataset: 'source-dataset',
|
|
202
|
-
},
|
|
203
|
-
}),
|
|
197
|
+
'~experimental_resource': {type: 'dataset', id: 'source-project.source-dataset'},
|
|
204
198
|
}),
|
|
205
199
|
)
|
|
206
200
|
})
|
|
207
201
|
|
|
208
202
|
it('should create resource when source has array sourceId and be projectless', () => {
|
|
209
|
-
const
|
|
210
|
-
|
|
203
|
+
const client = getClient(instance, {
|
|
204
|
+
apiVersion: '2024-11-12',
|
|
205
|
+
source: {mediaLibraryId: 'media-lib-123'},
|
|
206
|
+
})
|
|
211
207
|
|
|
212
208
|
expect(vi.mocked(createClient)).toHaveBeenCalledWith(
|
|
213
209
|
expect.objectContaining({
|
|
@@ -226,8 +222,10 @@ describe('clientStore', () => {
|
|
|
226
222
|
})
|
|
227
223
|
|
|
228
224
|
it('should create resource when canvas source is provided and be projectless', () => {
|
|
229
|
-
const
|
|
230
|
-
|
|
225
|
+
const client = getClient(instance, {
|
|
226
|
+
apiVersion: '2024-11-12',
|
|
227
|
+
source: {canvasId: 'canvas-123'},
|
|
228
|
+
})
|
|
231
229
|
|
|
232
230
|
expect(vi.mocked(createClient)).toHaveBeenCalledWith(
|
|
233
231
|
expect.objectContaining({
|
|
@@ -246,30 +244,26 @@ describe('clientStore', () => {
|
|
|
246
244
|
})
|
|
247
245
|
|
|
248
246
|
it('should create projectless client when source is provided, ignoring instance config', () => {
|
|
249
|
-
const
|
|
250
|
-
|
|
247
|
+
const client = getClient(instance, {
|
|
248
|
+
apiVersion: '2024-11-12',
|
|
249
|
+
source: {projectId: 'source-project', dataset: 'source-dataset'},
|
|
250
|
+
})
|
|
251
251
|
|
|
252
252
|
// Client should be projectless - source takes precedence, instance config is ignored
|
|
253
253
|
expect(client.config()).not.toHaveProperty('projectId')
|
|
254
254
|
expect(client.config()).not.toHaveProperty('dataset')
|
|
255
255
|
expect(client.config()).toEqual(
|
|
256
256
|
expect.objectContaining({
|
|
257
|
-
|
|
258
|
-
__sanity_internal_sourceId: {
|
|
259
|
-
projectId: 'source-project',
|
|
260
|
-
dataset: 'source-dataset',
|
|
261
|
-
},
|
|
262
|
-
}),
|
|
257
|
+
'~experimental_resource': {type: 'dataset', id: 'source-project.source-dataset'},
|
|
263
258
|
}),
|
|
264
259
|
)
|
|
265
260
|
})
|
|
266
261
|
|
|
267
262
|
it('should warn when both source and explicit projectId/dataset are provided', () => {
|
|
268
263
|
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
269
|
-
const source = datasetSource('source-project', 'source-dataset')
|
|
270
264
|
const client = getClient(instance, {
|
|
271
265
|
apiVersion: '2024-11-12',
|
|
272
|
-
source,
|
|
266
|
+
source: {projectId: 'source-project', dataset: 'source-dataset'},
|
|
273
267
|
projectId: 'explicit-project',
|
|
274
268
|
dataset: 'explicit-dataset',
|
|
275
269
|
})
|
|
@@ -284,13 +278,18 @@ describe('clientStore', () => {
|
|
|
284
278
|
})
|
|
285
279
|
|
|
286
280
|
it('should create different clients for different sources', () => {
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
281
|
+
const client1 = getClient(instance, {
|
|
282
|
+
apiVersion: '2024-11-12',
|
|
283
|
+
source: {projectId: 'source-project', dataset: 'source-dataset'},
|
|
284
|
+
})
|
|
285
|
+
const client2 = getClient(instance, {
|
|
286
|
+
apiVersion: '2024-11-12',
|
|
287
|
+
source: {mediaLibraryId: 'media-lib-123'},
|
|
288
|
+
})
|
|
289
|
+
const client3 = getClient(instance, {
|
|
290
|
+
apiVersion: '2024-11-12',
|
|
291
|
+
source: {canvasId: 'canvas-123'},
|
|
292
|
+
})
|
|
294
293
|
|
|
295
294
|
expect(client1).not.toBe(client2)
|
|
296
295
|
expect(client2).not.toBe(client3)
|
|
@@ -299,11 +298,14 @@ describe('clientStore', () => {
|
|
|
299
298
|
})
|
|
300
299
|
|
|
301
300
|
it('should reuse clients with identical source configurations', () => {
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const client2 = getClient(instance,
|
|
301
|
+
const client1 = getClient(instance, {
|
|
302
|
+
apiVersion: '2024-11-12',
|
|
303
|
+
source: {projectId: 'source-project', dataset: 'source-dataset'},
|
|
304
|
+
})
|
|
305
|
+
const client2 = getClient(instance, {
|
|
306
|
+
apiVersion: '2024-11-12',
|
|
307
|
+
source: {projectId: 'source-project', dataset: 'source-dataset'},
|
|
308
|
+
})
|
|
307
309
|
|
|
308
310
|
expect(client1).toBe(client2)
|
|
309
311
|
expect(vi.mocked(createClient)).toHaveBeenCalledTimes(1)
|
|
@@ -2,7 +2,12 @@ import {type ClientConfig, createClient, type SanityClient} from '@sanity/client
|
|
|
2
2
|
import {pick} from 'lodash-es'
|
|
3
3
|
|
|
4
4
|
import {getAuthMethodState, getTokenState} from '../auth/authStore'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type DocumentSource,
|
|
7
|
+
isCanvasSource,
|
|
8
|
+
isDatasetSource,
|
|
9
|
+
isMediaLibrarySource,
|
|
10
|
+
} from '../config/sanityConfig'
|
|
6
11
|
import {bindActionGlobally} from '../store/createActionBinder'
|
|
7
12
|
import {createStateSourceAction} from '../store/createStateSourceAction'
|
|
8
13
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
@@ -61,6 +66,11 @@ export interface ClientStoreState {
|
|
|
61
66
|
authMethod?: 'localstorage' | 'cookie'
|
|
62
67
|
}
|
|
63
68
|
|
|
69
|
+
interface ClientResource {
|
|
70
|
+
type: 'dataset' | 'media-library' | 'canvas'
|
|
71
|
+
id: string
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
/**
|
|
65
75
|
* Options used when retrieving a client instance from the client store.
|
|
66
76
|
*
|
|
@@ -170,13 +180,17 @@ export const getClient = bindActionGlobally(
|
|
|
170
180
|
|
|
171
181
|
const tokenFromState = state.get().token
|
|
172
182
|
const {clients, authMethod} = state.get()
|
|
173
|
-
const hasSource = !!options.source
|
|
174
|
-
let sourceId = options.source?.[SOURCE_ID]
|
|
175
183
|
|
|
176
|
-
let resource
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
let resource: ClientResource | undefined
|
|
185
|
+
|
|
186
|
+
if (options.source) {
|
|
187
|
+
if (isDatasetSource(options.source)) {
|
|
188
|
+
resource = {type: 'dataset', id: `${options.source.projectId}.${options.source.dataset}`}
|
|
189
|
+
} else if (isMediaLibrarySource(options.source)) {
|
|
190
|
+
resource = {type: 'media-library', id: options.source.mediaLibraryId}
|
|
191
|
+
} else if (isCanvasSource(options.source)) {
|
|
192
|
+
resource = {type: 'canvas', id: options.source.canvasId}
|
|
193
|
+
}
|
|
180
194
|
}
|
|
181
195
|
|
|
182
196
|
const projectId = options.projectId ?? instance.config.projectId
|
|
@@ -185,7 +199,7 @@ export const getClient = bindActionGlobally(
|
|
|
185
199
|
|
|
186
200
|
const effectiveOptions: ClientOptions = {
|
|
187
201
|
...DEFAULT_CLIENT_CONFIG,
|
|
188
|
-
...((options.scope === 'global' || !projectId ||
|
|
202
|
+
...((options.scope === 'global' || !projectId || resource) && {useProjectHostname: false}),
|
|
189
203
|
token: authMethod === 'cookie' ? undefined : (tokenFromState ?? undefined),
|
|
190
204
|
...options,
|
|
191
205
|
...(projectId && {projectId}),
|
|
@@ -197,7 +211,7 @@ export const getClient = bindActionGlobally(
|
|
|
197
211
|
// When a source is provided, don't use projectId/dataset - the client should be "projectless"
|
|
198
212
|
// The client code itself will ignore the non-source config, so we do this to prevent confusing the user.
|
|
199
213
|
// (ref: https://github.com/sanity-io/client/blob/5c23f81f5ab93a53f5b22b39845c867988508d84/src/data/dataMethods.ts#L691)
|
|
200
|
-
if (
|
|
214
|
+
if (resource) {
|
|
201
215
|
if (options.projectId || options.dataset) {
|
|
202
216
|
// eslint-disable-next-line no-console
|
|
203
217
|
console.warn(
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API for configuring SDK logging
|
|
3
|
+
*
|
|
4
|
+
* @module loggingConfig
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {configureLogging as _configureLogging, type LoggerConfig} from '../utils/logger'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Configure logging for the Sanity SDK
|
|
11
|
+
*
|
|
12
|
+
* This function allows you to control what logs are output by the SDK,
|
|
13
|
+
* making it easier to debug issues in development or production.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* **Zero-Config via Environment Variable (Recommended):**
|
|
17
|
+
*
|
|
18
|
+
* The SDK automatically reads the `DEBUG` environment variable, making it
|
|
19
|
+
* easy to enable logging without code changes:
|
|
20
|
+
*
|
|
21
|
+
* ```bash
|
|
22
|
+
* # Enable all SDK logging at debug level
|
|
23
|
+
* DEBUG=sanity:* npm start
|
|
24
|
+
*
|
|
25
|
+
* # Enable specific namespaces
|
|
26
|
+
* DEBUG=sanity:auth,sanity:document npm start
|
|
27
|
+
*
|
|
28
|
+
* # Enable trace level for all namespaces
|
|
29
|
+
* DEBUG=sanity:trace:* npm start
|
|
30
|
+
*
|
|
31
|
+
* # Enable internal/maintainer logs
|
|
32
|
+
* DEBUG=sanity:*:internal npm start
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* This matches the pattern used by Sanity CLI and Studio, making it familiar
|
|
36
|
+
* and easy for support teams to help troubleshoot issues.
|
|
37
|
+
*
|
|
38
|
+
* **Programmatic Configuration (Advanced):**
|
|
39
|
+
*
|
|
40
|
+
* For more control (custom handlers, dynamic configuration), call this function
|
|
41
|
+
* explicitly. Programmatic configuration overrides environment variables.
|
|
42
|
+
*
|
|
43
|
+
* **For Application Developers:**
|
|
44
|
+
* Use `info`, `warn`, or `error` levels to see high-level SDK activity
|
|
45
|
+
* without being overwhelmed by internal details.
|
|
46
|
+
*
|
|
47
|
+
* **For SDK Maintainers:**
|
|
48
|
+
* Use `debug` or `trace` levels with `internal: true` to see detailed
|
|
49
|
+
* information about store operations, RxJS streams, and state transitions.
|
|
50
|
+
*
|
|
51
|
+
* **Instance Context:**
|
|
52
|
+
* Logs automatically include instance information (projectId, dataset, instanceId)
|
|
53
|
+
* when available, making it easier to debug multi-instance scenarios:
|
|
54
|
+
* ```
|
|
55
|
+
* [INFO] [auth] [project:abc] [dataset:production] User logged in
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* **Available Namespaces:**
|
|
59
|
+
* - `sdk` - SDK initialization, configuration, and lifecycle
|
|
60
|
+
* - `auth` - Authentication and authorization (when instrumented in the future)
|
|
61
|
+
* - And more as logging is added to modules
|
|
62
|
+
*
|
|
63
|
+
* @example Zero-config via environment variable (recommended for debugging)
|
|
64
|
+
* ```bash
|
|
65
|
+
* # Just set DEBUG and run your app - no code changes needed!
|
|
66
|
+
* DEBUG=sanity:* npm start
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @example Programmatic configuration (application developer)
|
|
70
|
+
* ```ts
|
|
71
|
+
* import {configureLogging} from '@sanity/sdk'
|
|
72
|
+
*
|
|
73
|
+
* // Log warnings and errors for auth and document operations
|
|
74
|
+
* configureLogging({
|
|
75
|
+
* level: 'warn',
|
|
76
|
+
* namespaces: ['auth', 'document']
|
|
77
|
+
* })
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @example Programmatic configuration (SDK maintainer)
|
|
81
|
+
* ```ts
|
|
82
|
+
* import {configureLogging} from '@sanity/sdk'
|
|
83
|
+
*
|
|
84
|
+
* // Enable all logs including internal traces
|
|
85
|
+
* configureLogging({
|
|
86
|
+
* level: 'trace',
|
|
87
|
+
* namespaces: ['*'],
|
|
88
|
+
* internal: true
|
|
89
|
+
* })
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* @example Custom handler (for testing)
|
|
93
|
+
* ```ts
|
|
94
|
+
* import {configureLogging} from '@sanity/sdk'
|
|
95
|
+
*
|
|
96
|
+
* const logs: string[] = []
|
|
97
|
+
* configureLogging({
|
|
98
|
+
* level: 'info',
|
|
99
|
+
* namespaces: ['*'],
|
|
100
|
+
* handler: {
|
|
101
|
+
* error: (msg) => logs.push(msg),
|
|
102
|
+
* warn: (msg) => logs.push(msg),
|
|
103
|
+
* info: (msg) => logs.push(msg),
|
|
104
|
+
* debug: (msg) => logs.push(msg),
|
|
105
|
+
* trace: (msg) => logs.push(msg),
|
|
106
|
+
* }
|
|
107
|
+
* })
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @public
|
|
111
|
+
*/
|
|
112
|
+
export function configureLogging(config: LoggerConfig): void {
|
|
113
|
+
_configureLogging(config)
|
|
114
|
+
|
|
115
|
+
// Always log configuration (bypasses namespace filtering)
|
|
116
|
+
// This ensures users see the message regardless of which namespaces they enable
|
|
117
|
+
const configLevel = config.level || 'warn'
|
|
118
|
+
const shouldLog = ['info', 'debug', 'trace'].includes(configLevel) || configLevel === 'warn'
|
|
119
|
+
|
|
120
|
+
if (shouldLog && config.handler?.info) {
|
|
121
|
+
config.handler.info(`[${new Date().toISOString()}] [INFO] [sdk] Logging configured`, {
|
|
122
|
+
level: configLevel,
|
|
123
|
+
namespaces: config.namespaces || [],
|
|
124
|
+
internal: config.internal || false,
|
|
125
|
+
source: 'programmatic',
|
|
126
|
+
})
|
|
127
|
+
} else if (shouldLog) {
|
|
128
|
+
// eslint-disable-next-line no-console
|
|
129
|
+
console.info(`[${new Date().toISOString()}] [INFO] [sdk] Logging configured`, {
|
|
130
|
+
level: configLevel,
|
|
131
|
+
namespaces: config.namespaces || [],
|
|
132
|
+
internal: config.internal || false,
|
|
133
|
+
source: 'programmatic',
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Re-export types for public API
|
|
140
|
+
* @public
|
|
141
|
+
*/
|
|
142
|
+
export type {
|
|
143
|
+
InstanceContext,
|
|
144
|
+
LogContext,
|
|
145
|
+
Logger,
|
|
146
|
+
LoggerConfig,
|
|
147
|
+
LogLevel,
|
|
148
|
+
LogNamespace,
|
|
149
|
+
} from '../utils/logger'
|
|
@@ -2,6 +2,40 @@ import {type ClientPerspective, type StackablePerspective} from '@sanity/client'
|
|
|
2
2
|
|
|
3
3
|
import {type AuthConfig} from './authConfig'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A minimal Observable-compatible interface for subscribing to token changes.
|
|
7
|
+
* Any object with a `subscribe` method that follows this contract will work,
|
|
8
|
+
* including RxJS Observables. This avoids coupling the SDK to a specific
|
|
9
|
+
* reactive library.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export interface TokenSource {
|
|
14
|
+
/** Subscribe to token emissions. Emits `null` when logged out. */
|
|
15
|
+
subscribe(observer: {next: (token: string | null) => void}): {unsubscribe(): void}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Studio-specific configuration for the SDK.
|
|
20
|
+
* When present, the SDK operates in studio mode and derives auth from the
|
|
21
|
+
* provided token source instead of discovering tokens independently.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
export interface StudioConfig {
|
|
26
|
+
/** Reactive auth token source from the Studio's auth store. */
|
|
27
|
+
auth?: {
|
|
28
|
+
/**
|
|
29
|
+
* A reactive token source. The SDK subscribes and stays in sync — the
|
|
30
|
+
* Studio is the single authority for auth and handles token refresh.
|
|
31
|
+
*
|
|
32
|
+
* Optional because older Studios may not expose it. When absent, the
|
|
33
|
+
* SDK falls back to localStorage/cookie discovery.
|
|
34
|
+
*/
|
|
35
|
+
token?: TokenSource
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
5
39
|
/**
|
|
6
40
|
* Represents the minimal configuration required to identify a Sanity project.
|
|
7
41
|
* @public
|
|
@@ -31,6 +65,11 @@ export interface PerspectiveHandle {
|
|
|
31
65
|
export interface DatasetHandle<TDataset extends string = string, TProjectId extends string = string>
|
|
32
66
|
extends ProjectHandle<TProjectId>, PerspectiveHandle {
|
|
33
67
|
dataset?: TDataset
|
|
68
|
+
/**
|
|
69
|
+
* @beta
|
|
70
|
+
* Explicit source object to use for this operation.
|
|
71
|
+
*/
|
|
72
|
+
source?: DocumentSource
|
|
34
73
|
}
|
|
35
74
|
|
|
36
75
|
/**
|
|
@@ -79,51 +118,72 @@ export interface SanityConfig extends DatasetHandle, PerspectiveHandle {
|
|
|
79
118
|
*/
|
|
80
119
|
auth?: AuthConfig
|
|
81
120
|
/**
|
|
82
|
-
* Studio
|
|
83
|
-
*
|
|
121
|
+
* Studio configuration provided by a Sanity Studio workspace.
|
|
122
|
+
* When present, the SDK operates in studio mode and derives auth from the
|
|
123
|
+
* workspace's reactive token source — no manual configuration needed.
|
|
124
|
+
*
|
|
125
|
+
* @remarks Typically set automatically by `SanityApp` when it detects an
|
|
126
|
+
* `SDKStudioContext` provider. Can also be set explicitly for programmatic use.
|
|
127
|
+
*/
|
|
128
|
+
studio?: StudioConfig
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Studio mode configuration for use of the SDK in a Sanity Studio.
|
|
132
|
+
* @remarks Controls whether studio mode features are enabled.
|
|
133
|
+
* @deprecated Use `studio` instead, which provides richer integration
|
|
134
|
+
* with the Studio's workspace (auth token sync, etc.).
|
|
84
135
|
*/
|
|
85
136
|
studioMode?: {
|
|
86
137
|
enabled: boolean
|
|
87
138
|
}
|
|
88
|
-
}
|
|
89
139
|
|
|
90
|
-
|
|
140
|
+
/**
|
|
141
|
+
* @beta
|
|
142
|
+
* A list of named sources to use for this instance.
|
|
143
|
+
*/
|
|
144
|
+
sources?: Record<string, DocumentSource>
|
|
145
|
+
}
|
|
91
146
|
|
|
92
147
|
/**
|
|
93
148
|
* A document source can be used for querying.
|
|
149
|
+
* This will soon be the default way to identify where you are querying from.
|
|
94
150
|
*
|
|
95
151
|
* @beta
|
|
96
|
-
* @see datasetSource Construct a document source for a given projectId and dataset.
|
|
97
|
-
* @see mediaLibrarySource Construct a document source for a mediaLibraryId.
|
|
98
|
-
* @see canvasSource Construct a document source for a canvasId.
|
|
99
152
|
*/
|
|
100
|
-
export type DocumentSource =
|
|
101
|
-
[SOURCE_ID]: ['media-library', string] | ['canvas', string] | {projectId: string; dataset: string}
|
|
102
|
-
}
|
|
153
|
+
export type DocumentSource = DatasetSource | MediaLibrarySource | CanvasSource
|
|
103
154
|
|
|
104
155
|
/**
|
|
105
|
-
* Returns a document source for a projectId and dataset.
|
|
106
|
-
*
|
|
107
156
|
* @beta
|
|
108
157
|
*/
|
|
109
|
-
export
|
|
110
|
-
|
|
158
|
+
export type DatasetSource = {projectId: string; dataset: string}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @beta
|
|
162
|
+
*/
|
|
163
|
+
export type MediaLibrarySource = {mediaLibraryId: string}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @beta
|
|
167
|
+
*/
|
|
168
|
+
export type CanvasSource = {canvasId: string}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @beta
|
|
172
|
+
*/
|
|
173
|
+
export function isDatasetSource(source: DocumentSource): source is DatasetSource {
|
|
174
|
+
return 'projectId' in source && 'dataset' in source
|
|
111
175
|
}
|
|
112
176
|
|
|
113
177
|
/**
|
|
114
|
-
* Returns a document source for a Media Library.
|
|
115
|
-
*
|
|
116
178
|
* @beta
|
|
117
179
|
*/
|
|
118
|
-
export function
|
|
119
|
-
return
|
|
180
|
+
export function isMediaLibrarySource(source: DocumentSource): source is MediaLibrarySource {
|
|
181
|
+
return 'mediaLibraryId' in source
|
|
120
182
|
}
|
|
121
183
|
|
|
122
184
|
/**
|
|
123
|
-
* Returns a document source for a Canvas.
|
|
124
|
-
*
|
|
125
185
|
* @beta
|
|
126
186
|
*/
|
|
127
|
-
export function
|
|
128
|
-
return
|
|
187
|
+
export function isCanvasSource(source: DocumentSource): source is CanvasSource {
|
|
188
|
+
return 'canvasId' in source
|
|
129
189
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import {DocumentId, getPublishedId} from '@sanity/id-utils'
|
|
1
2
|
import {type SanityProjectionResult} from 'groq'
|
|
2
3
|
import {omit} from 'lodash-es'
|
|
3
4
|
|
|
4
5
|
import {type DocumentHandle} from '../config/sanityConfig'
|
|
5
|
-
import {
|
|
6
|
+
import {bindActionBySourceAndPerspective} from '../store/createActionBinder'
|
|
6
7
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
7
8
|
import {
|
|
8
9
|
createStateSourceAction,
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
type StateSource,
|
|
11
12
|
} from '../store/createStateSourceAction'
|
|
12
13
|
import {hashString} from '../utils/hashString'
|
|
13
|
-
import {
|
|
14
|
+
import {insecureRandomId} from '../utils/ids'
|
|
14
15
|
import {projectionStore} from './projectionStore'
|
|
15
16
|
import {type ProjectionStoreState, type ProjectionValuePending} from './types'
|
|
16
17
|
import {PROJECTION_STATE_CLEAR_DELAY, STABLE_EMPTY_PROJECTION, validateProjection} from './util'
|
|
@@ -70,21 +71,21 @@ export function getProjectionState(
|
|
|
70
71
|
/**
|
|
71
72
|
* @beta
|
|
72
73
|
*/
|
|
73
|
-
export const _getProjectionState =
|
|
74
|
+
export const _getProjectionState = bindActionBySourceAndPerspective(
|
|
74
75
|
projectionStore,
|
|
75
76
|
createStateSourceAction({
|
|
76
77
|
selector: (
|
|
77
78
|
{state}: SelectorContext<ProjectionStoreState>,
|
|
78
79
|
options: ProjectionOptions<string, string, string, string>,
|
|
79
80
|
): ProjectionValuePending<object> | undefined => {
|
|
80
|
-
const documentId = getPublishedId(options.documentId)
|
|
81
|
+
const documentId = getPublishedId(DocumentId(options.documentId))
|
|
81
82
|
const projectionHash = hashString(options.projection)
|
|
82
83
|
return state.values[documentId]?.[projectionHash] ?? STABLE_EMPTY_PROJECTION
|
|
83
84
|
},
|
|
84
85
|
onSubscribe: ({state}, options: ProjectionOptions<string, string, string, string>) => {
|
|
85
86
|
const {projection, ...docHandle} = options
|
|
86
87
|
const subscriptionId = insecureRandomId()
|
|
87
|
-
const documentId = getPublishedId(docHandle.documentId)
|
|
88
|
+
const documentId = getPublishedId(DocumentId(docHandle.documentId))
|
|
88
89
|
const validProjection = validateProjection(projection)
|
|
89
90
|
const projectionHash = hashString(validProjection)
|
|
90
91
|
|