@sanity/sdk 2.1.2 → 2.2.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 +14 -2
- package/dist/index.js +32 -12
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/projects/projects.test.ts +36 -1
- package/src/projects/projects.ts +15 -3
- package/src/query/queryStore.test.ts +86 -0
- package/src/query/queryStore.ts +34 -7
- package/src/query/queryStoreConstants.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Sanity SDK",
|
|
6
6
|
"keywords": [
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"prettier": "@sanity/prettier-config",
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@sanity/bifur-client": "^0.4.1",
|
|
46
|
-
"@sanity/client": "^7.
|
|
46
|
+
"@sanity/client": "^7.10.0",
|
|
47
47
|
"@sanity/comlink": "^3.0.4",
|
|
48
48
|
"@sanity/diff-match-patch": "^3.2.0",
|
|
49
49
|
"@sanity/diff-patch": "^6.0.0",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"@repo/config-eslint": "0.0.0",
|
|
74
74
|
"@repo/config-test": "0.0.1",
|
|
75
75
|
"@repo/package.config": "0.0.1",
|
|
76
|
-
"@repo/
|
|
77
|
-
"@repo/
|
|
76
|
+
"@repo/package.bundle": "3.82.0",
|
|
77
|
+
"@repo/tsconfig": "0.0.1"
|
|
78
78
|
},
|
|
79
79
|
"engines": {
|
|
80
80
|
"node": ">=20.0.0"
|
|
@@ -36,6 +36,41 @@ describe('projects', () => {
|
|
|
36
36
|
|
|
37
37
|
const result = await resolveProjects(instance)
|
|
38
38
|
expect(result).toEqual(projects)
|
|
39
|
-
expect(list).toHaveBeenCalledWith({includeMembers: false})
|
|
39
|
+
expect(list).toHaveBeenCalledWith({includeMembers: false, organizationId: undefined})
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('projects cache key generation', () => {
|
|
44
|
+
it('generates correct cache keys for different parameter combinations', async () => {
|
|
45
|
+
// Test the getKey function directly by creating a mock store
|
|
46
|
+
const mockGetKey = (
|
|
47
|
+
_instance: SanityInstance,
|
|
48
|
+
options?: {organizationId?: string; includeMembers?: boolean},
|
|
49
|
+
) => {
|
|
50
|
+
const orgKey = options?.organizationId ? `:org:${options.organizationId}` : ''
|
|
51
|
+
const membersKey = options?.includeMembers === false ? ':no-members' : ''
|
|
52
|
+
return `projects${orgKey}${membersKey}`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const mockInstance = {} as SanityInstance
|
|
56
|
+
|
|
57
|
+
// Test default behavior (no options)
|
|
58
|
+
const defaultKey = mockGetKey(mockInstance)
|
|
59
|
+
expect(defaultKey).toBe('projects')
|
|
60
|
+
|
|
61
|
+
// Test with organizationId only
|
|
62
|
+
const orgKey = mockGetKey(mockInstance, {organizationId: 'org123'})
|
|
63
|
+
expect(orgKey).toBe('projects:org:org123')
|
|
64
|
+
|
|
65
|
+
// Test with includeMembers: false only
|
|
66
|
+
const noMembersKey = mockGetKey(mockInstance, {includeMembers: false})
|
|
67
|
+
expect(noMembersKey).toBe('projects:no-members')
|
|
68
|
+
|
|
69
|
+
// Test with both parameters
|
|
70
|
+
const bothKey = mockGetKey(mockInstance, {
|
|
71
|
+
organizationId: 'org123',
|
|
72
|
+
includeMembers: false,
|
|
73
|
+
})
|
|
74
|
+
expect(bothKey).toBe('projects:org:org123:no-members')
|
|
40
75
|
})
|
|
41
76
|
})
|
package/src/projects/projects.ts
CHANGED
|
@@ -7,13 +7,25 @@ const API_VERSION = 'v2025-02-19'
|
|
|
7
7
|
|
|
8
8
|
const projects = createFetcherStore({
|
|
9
9
|
name: 'Projects',
|
|
10
|
-
getKey: () =>
|
|
11
|
-
|
|
10
|
+
getKey: (_instance, options?: {organizationId?: string; includeMembers?: boolean}) => {
|
|
11
|
+
const orgKey = options?.organizationId ? `:org:${options.organizationId}` : ''
|
|
12
|
+
const membersKey = options?.includeMembers === false ? ':no-members' : ''
|
|
13
|
+
return `projects${orgKey}${membersKey}`
|
|
14
|
+
},
|
|
15
|
+
fetcher: (instance) => (options?: {organizationId?: string; includeMembers?: boolean}) =>
|
|
12
16
|
getClientState(instance, {
|
|
13
17
|
apiVersion: API_VERSION,
|
|
14
18
|
scope: 'global',
|
|
19
|
+
requestTagPrefix: 'sanity.sdk.projects',
|
|
15
20
|
}).observable.pipe(
|
|
16
|
-
switchMap((client) =>
|
|
21
|
+
switchMap((client) => {
|
|
22
|
+
const organizationId = options?.organizationId
|
|
23
|
+
return client.observable.projects.list({
|
|
24
|
+
// client method has a type that expects false | undefined
|
|
25
|
+
includeMembers: !options?.includeMembers ? false : undefined,
|
|
26
|
+
organizationId,
|
|
27
|
+
})
|
|
28
|
+
}),
|
|
17
29
|
),
|
|
18
30
|
})
|
|
19
31
|
|
|
@@ -342,4 +342,90 @@ describe('queryStore', () => {
|
|
|
342
342
|
])
|
|
343
343
|
unsubscribe2()
|
|
344
344
|
})
|
|
345
|
+
|
|
346
|
+
it('separates cache entries by implicit perspective (instance.config)', async () => {
|
|
347
|
+
// Mock fetch to return different results based on perspective option
|
|
348
|
+
vi.mocked(fetch).mockImplementation(((_q, _p, options) => {
|
|
349
|
+
const perspective = (options as {perspective?: unknown})?.perspective
|
|
350
|
+
const result = perspective === 'published' ? [{_id: 'pub'}] : [{_id: 'drafts'}]
|
|
351
|
+
return of({result, syncTags: []}).pipe(delay(0)) as unknown as ReturnType<
|
|
352
|
+
SanityClient['observable']['fetch']
|
|
353
|
+
>
|
|
354
|
+
}) as SanityClient['observable']['fetch'])
|
|
355
|
+
|
|
356
|
+
const draftsInstance = createSanityInstance({
|
|
357
|
+
projectId: 'test',
|
|
358
|
+
dataset: 'test',
|
|
359
|
+
perspective: 'drafts',
|
|
360
|
+
})
|
|
361
|
+
const publishedInstance = createSanityInstance({
|
|
362
|
+
projectId: 'test',
|
|
363
|
+
dataset: 'test',
|
|
364
|
+
perspective: 'published',
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
// Same query/options, different implicit perspectives via instance.config
|
|
368
|
+
const sDrafts = getQueryState<{_id: string}[]>(draftsInstance, {query: '*[_type == "movie"]'})
|
|
369
|
+
const sPublished = getQueryState<{_id: string}[]>(publishedInstance, {
|
|
370
|
+
query: '*[_type == "movie"]',
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
const unsubDrafts = sDrafts.subscribe()
|
|
374
|
+
const unsubPublished = sPublished.subscribe()
|
|
375
|
+
|
|
376
|
+
const draftsResult = await firstValueFrom(
|
|
377
|
+
sDrafts.observable.pipe(filter((i) => i !== undefined)),
|
|
378
|
+
)
|
|
379
|
+
const publishedResult = await firstValueFrom(
|
|
380
|
+
sPublished.observable.pipe(filter((i) => i !== undefined)),
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
expect(draftsResult).toEqual([{_id: 'drafts'}])
|
|
384
|
+
expect(publishedResult).toEqual([{_id: 'pub'}])
|
|
385
|
+
|
|
386
|
+
unsubDrafts()
|
|
387
|
+
unsubPublished()
|
|
388
|
+
|
|
389
|
+
draftsInstance.dispose()
|
|
390
|
+
publishedInstance.dispose()
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
it('separates cache entries by explicit perspective in options', async () => {
|
|
394
|
+
vi.mocked(fetch).mockImplementation(((_q, _p, options) => {
|
|
395
|
+
const perspective = (options as {perspective?: unknown})?.perspective
|
|
396
|
+
const result = perspective === 'published' ? [{_id: 'pub'}] : [{_id: 'drafts'}]
|
|
397
|
+
return of({result, syncTags: []}).pipe(delay(0)) as unknown as ReturnType<
|
|
398
|
+
SanityClient['observable']['fetch']
|
|
399
|
+
>
|
|
400
|
+
}) as SanityClient['observable']['fetch'])
|
|
401
|
+
|
|
402
|
+
const base = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
403
|
+
|
|
404
|
+
const sDrafts = getQueryState<{_id: string}[]>(base, {
|
|
405
|
+
query: '*[_type == "movie"]',
|
|
406
|
+
perspective: 'drafts',
|
|
407
|
+
})
|
|
408
|
+
const sPublished = getQueryState<{_id: string}[]>(base, {
|
|
409
|
+
query: '*[_type == "movie"]',
|
|
410
|
+
perspective: 'published',
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
const unsubDrafts = sDrafts.subscribe()
|
|
414
|
+
const unsubPublished = sPublished.subscribe()
|
|
415
|
+
|
|
416
|
+
const draftsResult = await firstValueFrom(
|
|
417
|
+
sDrafts.observable.pipe(filter((i) => i !== undefined)),
|
|
418
|
+
)
|
|
419
|
+
const publishedResult = await firstValueFrom(
|
|
420
|
+
sPublished.observable.pipe(filter((i) => i !== undefined)),
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
expect(draftsResult).toEqual([{_id: 'drafts'}])
|
|
424
|
+
expect(publishedResult).toEqual([{_id: 'pub'}])
|
|
425
|
+
|
|
426
|
+
unsubDrafts()
|
|
427
|
+
unsubPublished()
|
|
428
|
+
|
|
429
|
+
base.dispose()
|
|
430
|
+
})
|
|
345
431
|
})
|
package/src/query/queryStore.ts
CHANGED
|
@@ -34,7 +34,11 @@ import {
|
|
|
34
34
|
import {type StoreState} from '../store/createStoreState'
|
|
35
35
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
36
36
|
import {insecureRandomId} from '../utils/ids'
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
QUERY_STATE_CLEAR_DELAY,
|
|
39
|
+
QUERY_STORE_API_VERSION,
|
|
40
|
+
QUERY_STORE_DEFAULT_PERSPECTIVE,
|
|
41
|
+
} from './queryStoreConstants'
|
|
38
42
|
import {
|
|
39
43
|
addSubscriber,
|
|
40
44
|
cancelQuery,
|
|
@@ -77,6 +81,28 @@ export const getQueryKey = (options: QueryOptions): string => JSON.stringify(opt
|
|
|
77
81
|
/** @beta */
|
|
78
82
|
export const parseQueryKey = (key: string): QueryOptions => JSON.parse(key)
|
|
79
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Ensures the query key includes an effective perspective so that
|
|
86
|
+
* implicit differences (e.g. different instance.config.perspective)
|
|
87
|
+
* don't collide in the dataset-scoped store.
|
|
88
|
+
*
|
|
89
|
+
* Since perspectives are unique, we can depend on the release stacks
|
|
90
|
+
* to be correct when we retrieve the results.
|
|
91
|
+
*
|
|
92
|
+
*/
|
|
93
|
+
function normalizeOptionsWithPerspective(
|
|
94
|
+
instance: SanityInstance,
|
|
95
|
+
options: QueryOptions,
|
|
96
|
+
): QueryOptions {
|
|
97
|
+
if (options.perspective !== undefined) return options
|
|
98
|
+
const instancePerspective = instance.config.perspective
|
|
99
|
+
return {
|
|
100
|
+
...options,
|
|
101
|
+
perspective:
|
|
102
|
+
instancePerspective !== undefined ? instancePerspective : QUERY_STORE_DEFAULT_PERSPECTIVE,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
80
106
|
const queryStore = defineStore<QueryStoreState>({
|
|
81
107
|
name: 'QueryStore',
|
|
82
108
|
getInitialState: () => ({queries: {}}),
|
|
@@ -255,16 +281,16 @@ export function getQueryState(
|
|
|
255
281
|
const _getQueryState = bindActionByDataset(
|
|
256
282
|
queryStore,
|
|
257
283
|
createStateSourceAction({
|
|
258
|
-
selector: ({state}: SelectorContext<QueryStoreState>, options: QueryOptions) => {
|
|
284
|
+
selector: ({state, instance}: SelectorContext<QueryStoreState>, options: QueryOptions) => {
|
|
259
285
|
if (state.error) throw state.error
|
|
260
|
-
const key = getQueryKey(options)
|
|
286
|
+
const key = getQueryKey(normalizeOptionsWithPerspective(instance, options))
|
|
261
287
|
const queryState = state.queries[key]
|
|
262
288
|
if (queryState?.error) throw queryState.error
|
|
263
289
|
return queryState?.result
|
|
264
290
|
},
|
|
265
|
-
onSubscribe: ({state}, options: QueryOptions) => {
|
|
291
|
+
onSubscribe: ({state, instance}, options: QueryOptions) => {
|
|
266
292
|
const subscriptionId = insecureRandomId()
|
|
267
|
-
const key = getQueryKey(options)
|
|
293
|
+
const key = getQueryKey(normalizeOptionsWithPerspective(instance, options))
|
|
268
294
|
|
|
269
295
|
state.set('addSubscriber', addSubscriber(key, subscriptionId))
|
|
270
296
|
|
|
@@ -314,8 +340,9 @@ export function resolveQuery(...args: Parameters<typeof _resolveQuery>): Promise
|
|
|
314
340
|
const _resolveQuery = bindActionByDataset(
|
|
315
341
|
queryStore,
|
|
316
342
|
({state, instance}, {signal, ...options}: ResolveQueryOptions) => {
|
|
317
|
-
const
|
|
318
|
-
const
|
|
343
|
+
const normalized = normalizeOptionsWithPerspective(instance, options)
|
|
344
|
+
const {getCurrent} = getQueryState(instance, normalized)
|
|
345
|
+
const key = getQueryKey(normalized)
|
|
319
346
|
|
|
320
347
|
const aborted$ = signal
|
|
321
348
|
? new Observable<void>((observer) => {
|