@sanity/sdk 2.5.0 → 2.6.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 +311 -20
- package/dist/index.js +195 -29
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/_exports/index.ts +15 -3
- 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 +39 -20
- package/src/query/queryStore.ts +3 -1
- package/src/store/createActionBinder.ts +17 -6
- package/src/store/createSanityInstance.test.ts +85 -1
- package/src/store/createSanityInstance.ts +53 -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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Sanity SDK",
|
|
6
6
|
"keywords": [
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@sanity/json-match": "^1.0.5",
|
|
52
52
|
"@sanity/message-protocol": "^0.18.0",
|
|
53
53
|
"@sanity/mutate": "^0.12.4",
|
|
54
|
-
"@sanity/types": "^
|
|
54
|
+
"@sanity/types": "^5.2.0",
|
|
55
55
|
"groq": "3.88.1-typegen-experimental.0",
|
|
56
56
|
"groq-js": "^1.19.0",
|
|
57
57
|
"lodash-es": "^4.17.21",
|
|
@@ -72,10 +72,10 @@
|
|
|
72
72
|
"vite": "^6.3.4",
|
|
73
73
|
"vitest": "^3.2.4",
|
|
74
74
|
"@repo/config-eslint": "0.0.0",
|
|
75
|
-
"@repo/config-test": "0.0.1",
|
|
76
75
|
"@repo/package.bundle": "3.82.0",
|
|
76
|
+
"@repo/tsconfig": "0.0.1",
|
|
77
77
|
"@repo/package.config": "0.0.1",
|
|
78
|
-
"@repo/
|
|
78
|
+
"@repo/config-test": "0.0.1"
|
|
79
79
|
},
|
|
80
80
|
"engines": {
|
|
81
81
|
"node": ">=20.19"
|
package/src/_exports/index.ts
CHANGED
|
@@ -76,13 +76,25 @@ export {
|
|
|
76
76
|
createProjectHandle,
|
|
77
77
|
} from '../config/handles'
|
|
78
78
|
export {
|
|
79
|
-
|
|
79
|
+
configureLogging,
|
|
80
|
+
type InstanceContext,
|
|
81
|
+
type LogContext,
|
|
82
|
+
type Logger,
|
|
83
|
+
type LoggerConfig,
|
|
84
|
+
type LogLevel,
|
|
85
|
+
type LogNamespace,
|
|
86
|
+
} from '../config/loggingConfig'
|
|
87
|
+
export {
|
|
88
|
+
type CanvasSource,
|
|
80
89
|
type DatasetHandle,
|
|
81
|
-
|
|
90
|
+
type DatasetSource,
|
|
82
91
|
type DocumentHandle,
|
|
83
92
|
type DocumentSource,
|
|
84
93
|
type DocumentTypeHandle,
|
|
85
|
-
|
|
94
|
+
isCanvasSource,
|
|
95
|
+
isDatasetSource,
|
|
96
|
+
isMediaLibrarySource,
|
|
97
|
+
type MediaLibrarySource,
|
|
86
98
|
type PerspectiveHandle,
|
|
87
99
|
type ProjectHandle,
|
|
88
100
|
type ReleasePerspective,
|
|
@@ -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'
|
|
@@ -31,6 +31,16 @@ export interface PerspectiveHandle {
|
|
|
31
31
|
export interface DatasetHandle<TDataset extends string = string, TProjectId extends string = string>
|
|
32
32
|
extends ProjectHandle<TProjectId>, PerspectiveHandle {
|
|
33
33
|
dataset?: TDataset
|
|
34
|
+
/**
|
|
35
|
+
* @beta
|
|
36
|
+
* The name of the source to use for this operation.
|
|
37
|
+
*/
|
|
38
|
+
sourceName?: string
|
|
39
|
+
/**
|
|
40
|
+
* @beta
|
|
41
|
+
* Explicit source object to use for this operation.
|
|
42
|
+
*/
|
|
43
|
+
source?: DocumentSource
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
/**
|
|
@@ -85,45 +95,54 @@ export interface SanityConfig extends DatasetHandle, PerspectiveHandle {
|
|
|
85
95
|
studioMode?: {
|
|
86
96
|
enabled: boolean
|
|
87
97
|
}
|
|
88
|
-
}
|
|
89
98
|
|
|
90
|
-
|
|
99
|
+
/**
|
|
100
|
+
* @beta
|
|
101
|
+
* A list of named sources to use for this instance.
|
|
102
|
+
*/
|
|
103
|
+
sources?: Record<string, DocumentSource>
|
|
104
|
+
}
|
|
91
105
|
|
|
92
106
|
/**
|
|
93
107
|
* A document source can be used for querying.
|
|
108
|
+
* This will soon be the default way to identify where you are querying from.
|
|
94
109
|
*
|
|
95
110
|
* @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
111
|
*/
|
|
100
|
-
export type DocumentSource =
|
|
101
|
-
|
|
102
|
-
|
|
112
|
+
export type DocumentSource = DatasetSource | MediaLibrarySource | CanvasSource
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @beta
|
|
116
|
+
*/
|
|
117
|
+
export type DatasetSource = {projectId: string; dataset: string}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @beta
|
|
121
|
+
*/
|
|
122
|
+
export type MediaLibrarySource = {mediaLibraryId: string}
|
|
103
123
|
|
|
104
124
|
/**
|
|
105
|
-
* Returns a document source for a projectId and dataset.
|
|
106
|
-
*
|
|
107
125
|
* @beta
|
|
108
126
|
*/
|
|
109
|
-
export
|
|
110
|
-
|
|
127
|
+
export type CanvasSource = {canvasId: string}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @beta
|
|
131
|
+
*/
|
|
132
|
+
export function isDatasetSource(source: DocumentSource): source is DatasetSource {
|
|
133
|
+
return 'projectId' in source && 'dataset' in source
|
|
111
134
|
}
|
|
112
135
|
|
|
113
136
|
/**
|
|
114
|
-
* Returns a document source for a Media Library.
|
|
115
|
-
*
|
|
116
137
|
* @beta
|
|
117
138
|
*/
|
|
118
|
-
export function
|
|
119
|
-
return
|
|
139
|
+
export function isMediaLibrarySource(source: DocumentSource): source is MediaLibrarySource {
|
|
140
|
+
return 'mediaLibraryId' in source
|
|
120
141
|
}
|
|
121
142
|
|
|
122
143
|
/**
|
|
123
|
-
* Returns a document source for a Canvas.
|
|
124
|
-
*
|
|
125
144
|
* @beta
|
|
126
145
|
*/
|
|
127
|
-
export function
|
|
128
|
-
return
|
|
146
|
+
export function isCanvasSource(source: DocumentSource): source is CanvasSource {
|
|
147
|
+
return 'canvasId' in source
|
|
129
148
|
}
|
package/src/query/queryStore.ts
CHANGED
|
@@ -58,7 +58,9 @@ export interface QueryOptions<
|
|
|
58
58
|
TQuery extends string = string,
|
|
59
59
|
TDataset extends string = string,
|
|
60
60
|
TProjectId extends string = string,
|
|
61
|
-
>
|
|
61
|
+
>
|
|
62
|
+
extends
|
|
63
|
+
Pick<ResponseQueryOptions, 'useCdn' | 'cache' | 'next' | 'cacheMode' | 'tag'>,
|
|
62
64
|
DatasetHandle<TDataset, TProjectId> {
|
|
63
65
|
query: TQuery
|
|
64
66
|
params?: Record<string, unknown>
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type DocumentSource,
|
|
3
|
+
isCanvasSource,
|
|
4
|
+
isDatasetSource,
|
|
5
|
+
isMediaLibrarySource,
|
|
6
|
+
} from '../config/sanityConfig'
|
|
2
7
|
import {type SanityInstance} from './createSanityInstance'
|
|
3
8
|
import {createStoreInstance, type StoreInstance} from './createStoreInstance'
|
|
4
9
|
import {type StoreState} from './createStoreState'
|
|
@@ -157,12 +162,18 @@ export const bindActionBySource = createActionBinder<
|
|
|
157
162
|
[{source?: DocumentSource}, ...unknown[]]
|
|
158
163
|
>((instance, {source}) => {
|
|
159
164
|
if (source) {
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
let id: string | undefined
|
|
166
|
+
if (isDatasetSource(source)) {
|
|
167
|
+
id = `${source.projectId}.${source.dataset}`
|
|
168
|
+
} else if (isMediaLibrarySource(source)) {
|
|
169
|
+
id = `media-library:${source.mediaLibraryId}`
|
|
170
|
+
} else if (isCanvasSource(source)) {
|
|
171
|
+
id = `canvas:${source.canvasId}`
|
|
172
|
+
}
|
|
165
173
|
|
|
174
|
+
if (!id) throw new Error(`Received invalid source: ${JSON.stringify(source)}`)
|
|
175
|
+
return {name: id}
|
|
176
|
+
}
|
|
166
177
|
const {projectId, dataset} = instance.config
|
|
167
178
|
|
|
168
179
|
if (!projectId || !dataset) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
1
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
2
2
|
|
|
3
|
+
import {configureLogging, type LogHandler, resetLogging} from '../utils/logger'
|
|
3
4
|
import {createSanityInstance} from './createSanityInstance'
|
|
4
5
|
|
|
5
6
|
describe('createSanityInstance', () => {
|
|
@@ -81,4 +82,87 @@ describe('createSanityInstance', () => {
|
|
|
81
82
|
const child = parent.createChild({auth: {token: 'my-token'}})
|
|
82
83
|
expect(child.config.auth).toEqual({apiHost: 'api.sanity.work', token: 'my-token'})
|
|
83
84
|
})
|
|
85
|
+
|
|
86
|
+
describe('logging', () => {
|
|
87
|
+
const mockHandler: LogHandler = {
|
|
88
|
+
error: vi.fn(),
|
|
89
|
+
warn: vi.fn(),
|
|
90
|
+
info: vi.fn(),
|
|
91
|
+
debug: vi.fn(),
|
|
92
|
+
trace: vi.fn(),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
vi.clearAllMocks()
|
|
97
|
+
configureLogging({
|
|
98
|
+
level: 'debug',
|
|
99
|
+
namespaces: ['sdk'],
|
|
100
|
+
handler: mockHandler,
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
afterEach(() => {
|
|
105
|
+
resetLogging()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('should log instance creation at info level', () => {
|
|
109
|
+
createSanityInstance({projectId: 'test-proj', dataset: 'test-ds'})
|
|
110
|
+
|
|
111
|
+
expect(mockHandler.info).toHaveBeenCalledWith(
|
|
112
|
+
expect.stringContaining('[INFO] [sdk]'),
|
|
113
|
+
expect.objectContaining({
|
|
114
|
+
hasProjectId: true,
|
|
115
|
+
hasDataset: true,
|
|
116
|
+
}),
|
|
117
|
+
)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should log configuration details at debug level', () => {
|
|
121
|
+
createSanityInstance({projectId: 'test-proj', dataset: 'test-ds'})
|
|
122
|
+
|
|
123
|
+
expect(mockHandler.debug).toHaveBeenCalledWith(
|
|
124
|
+
expect.stringContaining('[DEBUG] [sdk]'),
|
|
125
|
+
expect.objectContaining({
|
|
126
|
+
projectId: 'test-proj',
|
|
127
|
+
dataset: 'test-ds',
|
|
128
|
+
}),
|
|
129
|
+
)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should log instance disposal', () => {
|
|
133
|
+
const instance = createSanityInstance({projectId: 'test-proj'})
|
|
134
|
+
vi.clearAllMocks() // Clear creation logs
|
|
135
|
+
|
|
136
|
+
instance.dispose()
|
|
137
|
+
|
|
138
|
+
expect(mockHandler.info).toHaveBeenCalledWith(
|
|
139
|
+
expect.stringContaining('Instance disposed'),
|
|
140
|
+
expect.anything(),
|
|
141
|
+
)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('should log child instance creation at debug level', () => {
|
|
145
|
+
const parent = createSanityInstance({projectId: 'parent-proj'})
|
|
146
|
+
vi.clearAllMocks() // Clear parent creation logs
|
|
147
|
+
|
|
148
|
+
parent.createChild({dataset: 'child-ds'})
|
|
149
|
+
|
|
150
|
+
expect(mockHandler.debug).toHaveBeenCalledWith(
|
|
151
|
+
expect.stringContaining('Creating child instance'),
|
|
152
|
+
expect.objectContaining({
|
|
153
|
+
overridingDataset: true,
|
|
154
|
+
}),
|
|
155
|
+
)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('should include instance context in logs', () => {
|
|
159
|
+
createSanityInstance({projectId: 'my-project', dataset: 'my-dataset'})
|
|
160
|
+
|
|
161
|
+
// Check that logs include the instance context (project and dataset)
|
|
162
|
+
expect(mockHandler.info).toHaveBeenCalledWith(
|
|
163
|
+
expect.stringMatching(/\[project:my-project\].*\[dataset:my-dataset\]/),
|
|
164
|
+
expect.anything(),
|
|
165
|
+
)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
84
168
|
})
|