@sanity/sdk 0.0.0-alpha.21 → 0.0.0-alpha.23
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 +428 -325
- package/dist/index.js +1618 -1553
- package/dist/index.js.map +1 -1
- package/package.json +6 -7
- package/src/_exports/index.ts +31 -30
- package/src/auth/authStore.test.ts +149 -104
- package/src/auth/authStore.ts +51 -100
- package/src/auth/handleAuthCallback.test.ts +67 -34
- package/src/auth/handleAuthCallback.ts +8 -7
- package/src/auth/logout.test.ts +61 -29
- package/src/auth/logout.ts +26 -28
- package/src/auth/refreshStampedToken.test.ts +9 -9
- package/src/auth/refreshStampedToken.ts +62 -56
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +5 -5
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +45 -47
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -5
- package/src/auth/subscribeToStorageEventsAndSetToken.ts +22 -24
- package/src/client/clientStore.test.ts +131 -67
- package/src/client/clientStore.ts +117 -116
- package/src/comlink/controller/actions/destroyController.test.ts +38 -13
- package/src/comlink/controller/actions/destroyController.ts +11 -15
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +56 -27
- package/src/comlink/controller/actions/getOrCreateChannel.ts +37 -35
- package/src/comlink/controller/actions/getOrCreateController.test.ts +27 -16
- package/src/comlink/controller/actions/getOrCreateController.ts +23 -22
- package/src/comlink/controller/actions/releaseChannel.test.ts +37 -13
- package/src/comlink/controller/actions/releaseChannel.ts +22 -21
- package/src/comlink/controller/comlinkControllerStore.test.ts +65 -36
- package/src/comlink/controller/comlinkControllerStore.ts +44 -5
- package/src/comlink/node/actions/getOrCreateNode.test.ts +31 -15
- package/src/comlink/node/actions/getOrCreateNode.ts +30 -29
- package/src/comlink/node/actions/releaseNode.test.ts +75 -55
- package/src/comlink/node/actions/releaseNode.ts +19 -21
- package/src/comlink/node/comlinkNodeStore.test.ts +6 -11
- package/src/comlink/node/comlinkNodeStore.ts +22 -5
- package/src/config/authConfig.ts +79 -0
- package/src/config/sanityConfig.ts +48 -0
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +18 -5
- package/src/document/actions.test.ts +22 -10
- package/src/document/actions.ts +44 -56
- package/src/document/applyDocumentActions.test.ts +96 -36
- package/src/document/applyDocumentActions.ts +140 -99
- package/src/document/documentStore.test.ts +103 -155
- package/src/document/documentStore.ts +247 -237
- package/src/document/listen.ts +56 -55
- package/src/document/patchOperations.ts +0 -43
- package/src/document/permissions.test.ts +25 -12
- package/src/document/permissions.ts +11 -4
- package/src/document/processActions.test.ts +41 -8
- package/src/document/reducers.test.ts +87 -16
- package/src/document/reducers.ts +2 -2
- package/src/document/sharedListener.test.ts +34 -16
- package/src/document/sharedListener.ts +33 -11
- package/src/preview/getPreviewState.test.ts +40 -39
- package/src/preview/getPreviewState.ts +68 -56
- package/src/preview/previewConstants.ts +43 -0
- package/src/preview/previewQuery.test.ts +1 -1
- package/src/preview/previewQuery.ts +4 -5
- package/src/preview/previewStore.test.ts +13 -58
- package/src/preview/previewStore.ts +7 -21
- package/src/preview/resolvePreview.test.ts +33 -104
- package/src/preview/resolvePreview.ts +11 -21
- package/src/preview/subscribeToStateAndFetchBatches.test.ts +96 -97
- package/src/preview/subscribeToStateAndFetchBatches.ts +85 -81
- package/src/preview/util.ts +1 -0
- package/src/project/project.test.ts +3 -3
- package/src/project/project.ts +28 -5
- package/src/projection/getProjectionState.test.ts +69 -49
- package/src/projection/getProjectionState.ts +42 -50
- package/src/projection/projectionQuery.ts +1 -1
- package/src/projection/projectionStore.test.ts +13 -51
- package/src/projection/projectionStore.ts +6 -18
- package/src/projection/resolveProjection.test.ts +32 -127
- package/src/projection/resolveProjection.ts +15 -28
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +105 -90
- package/src/projection/subscribeToStateAndFetchBatches.ts +94 -81
- package/src/projection/util.ts +2 -0
- package/src/projects/projects.test.ts +13 -4
- package/src/projects/projects.ts +6 -1
- package/src/query/queryStore.test.ts +10 -47
- package/src/query/queryStore.ts +151 -133
- package/src/query/queryStoreConstants.ts +2 -0
- package/src/store/createActionBinder.test.ts +153 -0
- package/src/store/createActionBinder.ts +176 -0
- package/src/store/createSanityInstance.test.ts +84 -0
- package/src/store/createSanityInstance.ts +124 -0
- package/src/store/createStateSourceAction.test.ts +196 -0
- package/src/store/createStateSourceAction.ts +260 -0
- package/src/store/createStoreInstance.test.ts +81 -0
- package/src/store/createStoreInstance.ts +80 -0
- package/src/store/createStoreState.test.ts +85 -0
- package/src/store/createStoreState.ts +92 -0
- package/src/store/defineStore.test.ts +18 -0
- package/src/store/defineStore.ts +81 -0
- package/src/users/reducers.test.ts +318 -0
- package/src/users/reducers.ts +88 -0
- package/src/users/types.ts +46 -4
- package/src/users/usersConstants.ts +4 -0
- package/src/users/usersStore.test.ts +350 -223
- package/src/users/usersStore.ts +285 -149
- package/src/utils/createFetcherStore.test.ts +6 -7
- package/src/utils/createFetcherStore.ts +150 -153
- package/src/{common/util.test.ts → utils/hashString.test.ts} +1 -1
- package/src/auth/fetchLoginUrls.test.ts +0 -163
- package/src/auth/fetchLoginUrls.ts +0 -74
- package/src/common/createLiveEventSubscriber.test.ts +0 -121
- package/src/common/createLiveEventSubscriber.ts +0 -55
- package/src/common/types.ts +0 -4
- package/src/instance/identity.test.ts +0 -46
- package/src/instance/identity.ts +0 -29
- package/src/instance/sanityInstance.test.ts +0 -77
- package/src/instance/sanityInstance.ts +0 -57
- package/src/instance/types.ts +0 -37
- package/src/preview/getPreviewProjection.ts +0 -45
- package/src/resources/README.md +0 -370
- package/src/resources/createAction.test.ts +0 -101
- package/src/resources/createAction.ts +0 -44
- package/src/resources/createResource.test.ts +0 -112
- package/src/resources/createResource.ts +0 -102
- package/src/resources/createStateSourceAction.test.ts +0 -114
- package/src/resources/createStateSourceAction.ts +0 -83
- package/src/resources/createStore.test.ts +0 -67
- package/src/resources/createStore.ts +0 -46
- package/src/store/createStore.test.ts +0 -108
- package/src/store/createStore.ts +0 -106
- /package/src/{common/util.ts → utils/hashString.ts} +0 -0
|
@@ -1,87 +1,151 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import {createClient, type SanityClient} from '@sanity/client'
|
|
2
|
+
import {Subject} from 'rxjs'
|
|
3
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
3
4
|
|
|
4
5
|
import {getTokenState} from '../auth/authStore'
|
|
5
|
-
import {createSanityInstance} from '../
|
|
6
|
-
import {
|
|
7
|
-
import {type ClientOptions, getClient, getClientState} from './clientStore'
|
|
6
|
+
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
7
|
+
import {getClient, getClientState} from './clientStore'
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// Mock dependencies
|
|
10
|
+
vi.mock('@sanity/client')
|
|
10
11
|
|
|
11
|
-
vi.mock('../auth/authStore'
|
|
12
|
-
const subject = new ReplaySubject(1)
|
|
13
|
-
subject.next('initial-token')
|
|
12
|
+
vi.mock('../auth/authStore')
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
getTokenState: vi.fn().mockReturnValue({observable: subject}),
|
|
17
|
-
}
|
|
18
|
-
})
|
|
14
|
+
let instance: SanityInstance
|
|
19
15
|
|
|
20
16
|
beforeEach(() => {
|
|
21
|
-
|
|
17
|
+
vi.resetAllMocks()
|
|
18
|
+
vi.mocked(getTokenState).mockReturnValue({
|
|
19
|
+
getCurrent: vi.fn().mockReturnValue('initial-token'),
|
|
20
|
+
subscribe: vi.fn(),
|
|
21
|
+
observable: new Subject(),
|
|
22
|
+
})
|
|
23
|
+
vi.mocked(createClient).mockImplementation(
|
|
24
|
+
(clientConfig) => ({config: () => clientConfig}) as SanityClient,
|
|
25
|
+
)
|
|
26
|
+
instance = createSanityInstance({projectId: 'test-project', dataset: 'test-dataset'})
|
|
22
27
|
})
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const client1 = getClient(instance, {apiVersion: 'vX'})
|
|
28
|
-
const client2 = getClient(instance, {apiVersion: 'vX'})
|
|
29
|
-
expect(client1).toBe(client2)
|
|
30
|
-
})
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
instance.dispose()
|
|
31
|
+
})
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
describe('clientStore', () => {
|
|
34
|
+
describe('getClient', () => {
|
|
35
|
+
it('should create a client with default configuration', () => {
|
|
36
|
+
const client = getClient(instance, {apiVersion: '2024-11-12'})
|
|
37
|
+
|
|
38
|
+
const defaultConfiguration = {
|
|
39
|
+
useCdn: false,
|
|
40
|
+
ignoreBrowserTokenWarning: true,
|
|
41
|
+
allowReconfigure: false,
|
|
42
|
+
requestTagPrefix: 'sanity.sdk',
|
|
43
|
+
projectId: 'test-project',
|
|
44
|
+
dataset: 'test-dataset',
|
|
45
|
+
token: 'initial-token',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
expect(vi.mocked(createClient)).toHaveBeenCalledWith({
|
|
49
|
+
...defaultConfiguration,
|
|
50
|
+
apiVersion: '2024-11-12',
|
|
51
|
+
})
|
|
52
|
+
expect(client.config()).toEqual({
|
|
53
|
+
...defaultConfiguration,
|
|
54
|
+
apiVersion: '2024-11-12',
|
|
55
|
+
})
|
|
38
56
|
})
|
|
39
|
-
const projectClient = getClient(instance, {apiVersion: 'vX', scope: 'project'})
|
|
40
|
-
const globalClient = getClient(instance, {apiVersion: 'vX', scope: 'global'})
|
|
41
57
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
expect(client1.config().token).toBe('initial-token')
|
|
52
|
-
expect(client1.config().token).toBe(client2.config().token)
|
|
53
|
-
|
|
54
|
-
token$.next('updated-token')
|
|
55
|
-
const client3 = getClient(instance, {apiVersion: 'vX'})
|
|
56
|
-
const client4 = getClient(instance, {apiVersion: 'vX'})
|
|
57
|
-
expect(client3).toBe(client4)
|
|
58
|
-
expect(client3.config().token).toBe('updated-token')
|
|
59
|
-
expect(client3.config().token).toBe(client4.config().token)
|
|
60
|
-
})
|
|
61
|
-
})
|
|
58
|
+
it('should throw when using disallowed configuration keys', () => {
|
|
59
|
+
expect(() =>
|
|
60
|
+
getClient(instance, {
|
|
61
|
+
apiVersion: '2024-11-12',
|
|
62
|
+
// @ts-expect-error Testing invalid key
|
|
63
|
+
illegalKey: 'foo',
|
|
64
|
+
}),
|
|
65
|
+
).toThrowError(/unsupported properties: illegalKey/)
|
|
66
|
+
})
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const clientState = getClientState(instance, options)
|
|
68
|
+
it('should reuse clients with identical configurations', () => {
|
|
69
|
+
const options = {apiVersion: '2024-11-12', useCdn: true}
|
|
70
|
+
const client1 = getClient(instance, options)
|
|
71
|
+
const client2 = getClient(instance, options)
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
expect(client1).toBe(client2)
|
|
74
|
+
expect(vi.mocked(createClient)).toHaveBeenCalledTimes(1)
|
|
75
|
+
})
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
it('should create new clients when configuration changes', () => {
|
|
78
|
+
const client1 = getClient(instance, {apiVersion: '2024-11-12'})
|
|
79
|
+
const client2 = getClient(instance, {apiVersion: '2023-08-01'})
|
|
73
80
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
),
|
|
79
|
-
)
|
|
81
|
+
expect(client1).not.toBe(client2)
|
|
82
|
+
expect(vi.mocked(createClient)).toHaveBeenCalledTimes(2)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
describe('token handling', () => {
|
|
87
|
+
it('should reset clients when token changes', () => {
|
|
88
|
+
// Initial client with first token
|
|
89
|
+
const tokenState = getTokenState(instance)
|
|
90
|
+
vi.mocked(tokenState.getCurrent).mockReturnValue('first-token')
|
|
91
|
+
const client1 = getClient(instance, {apiVersion: '2024-11-12'})
|
|
92
|
+
|
|
93
|
+
// Simulate token change
|
|
94
|
+
vi.mocked(tokenState.getCurrent).mockReturnValue('new-token')
|
|
95
|
+
const token$ = tokenState.observable as Subject<string>
|
|
96
|
+
token$.next('new-token')
|
|
97
|
+
|
|
98
|
+
// New client should be created with new token
|
|
99
|
+
const client2 = getClient(instance, {apiVersion: '2024-11-12'})
|
|
100
|
+
|
|
101
|
+
expect(client1).not.toBe(client2)
|
|
102
|
+
expect(vi.mocked(createClient)).toHaveBeenCalledWith(
|
|
103
|
+
expect.objectContaining({
|
|
104
|
+
token: 'new-token',
|
|
105
|
+
}),
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
})
|
|
83
109
|
|
|
84
|
-
|
|
85
|
-
|
|
110
|
+
describe('getClientState', () => {
|
|
111
|
+
it('should provide a state source that emits client changes', async () => {
|
|
112
|
+
// Get initial client state with a specific configuration
|
|
113
|
+
const state = getClientState(instance, {apiVersion: '2024-11-12'})
|
|
114
|
+
|
|
115
|
+
// Get initial client
|
|
116
|
+
const initialClient = state.getCurrent()
|
|
117
|
+
expect(initialClient).toBeDefined()
|
|
118
|
+
|
|
119
|
+
// Setup a spy to track emissions from the observable
|
|
120
|
+
const nextSpy = vi.fn()
|
|
121
|
+
const subscription = state.observable.subscribe(nextSpy)
|
|
122
|
+
|
|
123
|
+
// Should have emitted once initially
|
|
124
|
+
expect(nextSpy).toHaveBeenCalledTimes(1)
|
|
125
|
+
expect(nextSpy).toHaveBeenCalledWith(initialClient)
|
|
126
|
+
|
|
127
|
+
// Simulate token change
|
|
128
|
+
const tokenState = getTokenState(instance)
|
|
129
|
+
vi.mocked(tokenState.getCurrent).mockReturnValue('updated-token')
|
|
130
|
+
const token$ = tokenState.observable as Subject<string>
|
|
131
|
+
token$.next('updated-token')
|
|
132
|
+
|
|
133
|
+
// Should emit a new client instance
|
|
134
|
+
expect(nextSpy).toHaveBeenCalledTimes(2)
|
|
135
|
+
|
|
136
|
+
// The new client should be different from the initial one
|
|
137
|
+
const updatedClient = nextSpy.mock.calls[1][0]
|
|
138
|
+
expect(updatedClient).not.toBe(initialClient)
|
|
139
|
+
|
|
140
|
+
// The updated client should have the new token
|
|
141
|
+
expect(updatedClient.config()).toEqual(
|
|
142
|
+
expect.objectContaining({
|
|
143
|
+
token: 'updated-token',
|
|
144
|
+
}),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
// Clean up subscription
|
|
148
|
+
subscription.unsubscribe()
|
|
149
|
+
})
|
|
86
150
|
})
|
|
87
151
|
})
|
|
@@ -1,23 +1,61 @@
|
|
|
1
1
|
import {type ClientConfig, createClient, type SanityClient} from '@sanity/client'
|
|
2
|
-
import {
|
|
2
|
+
import {pick} from 'lodash-es'
|
|
3
3
|
|
|
4
4
|
import {getTokenState} from '../auth/authStore'
|
|
5
|
-
import {type
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {createStateSourceAction} from '../resources/createStateSourceAction'
|
|
5
|
+
import {type DatasetHandle} from '../config/sanityConfig'
|
|
6
|
+
import {bindActionGlobally} from '../store/createActionBinder'
|
|
7
|
+
import {createStateSourceAction} from '../store/createStateSourceAction'
|
|
8
|
+
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
10
9
|
|
|
11
10
|
const DEFAULT_API_VERSION = '2024-11-12'
|
|
12
11
|
const DEFAULT_REQUEST_TAG_PREFIX = 'sanity.sdk'
|
|
13
12
|
|
|
13
|
+
type AllowedClientConfigKey =
|
|
14
|
+
| 'useCdn'
|
|
15
|
+
| 'token'
|
|
16
|
+
| 'perspective'
|
|
17
|
+
| 'apiHost'
|
|
18
|
+
| 'proxy'
|
|
19
|
+
| 'withCredentials'
|
|
20
|
+
| 'timeout'
|
|
21
|
+
| 'maxRetries'
|
|
22
|
+
| 'dataset'
|
|
23
|
+
| 'projectId'
|
|
24
|
+
| 'requestTagPrefix'
|
|
25
|
+
| 'useProjectHostname'
|
|
26
|
+
|
|
27
|
+
const allowedKeys = Object.keys({
|
|
28
|
+
apiHost: null,
|
|
29
|
+
useCdn: null,
|
|
30
|
+
token: null,
|
|
31
|
+
perspective: null,
|
|
32
|
+
proxy: null,
|
|
33
|
+
withCredentials: null,
|
|
34
|
+
timeout: null,
|
|
35
|
+
maxRetries: null,
|
|
36
|
+
dataset: null,
|
|
37
|
+
projectId: null,
|
|
38
|
+
scope: null,
|
|
39
|
+
apiVersion: null,
|
|
40
|
+
requestTagPrefix: null,
|
|
41
|
+
useProjectHostname: null,
|
|
42
|
+
} satisfies Record<keyof ClientOptions, null>) as (keyof ClientOptions)[]
|
|
43
|
+
|
|
44
|
+
const DEFAULT_CLIENT_CONFIG: ClientConfig = {
|
|
45
|
+
apiVersion: DEFAULT_API_VERSION,
|
|
46
|
+
useCdn: false,
|
|
47
|
+
ignoreBrowserTokenWarning: true,
|
|
48
|
+
allowReconfigure: false,
|
|
49
|
+
requestTagPrefix: DEFAULT_REQUEST_TAG_PREFIX,
|
|
50
|
+
}
|
|
51
|
+
|
|
14
52
|
/**
|
|
15
53
|
* States tracked by the client store
|
|
16
54
|
* @public
|
|
17
55
|
*/
|
|
18
|
-
export interface
|
|
19
|
-
|
|
20
|
-
|
|
56
|
+
export interface ClientStoreState {
|
|
57
|
+
token: string | null
|
|
58
|
+
clients: {[TKey in string]?: SanityClient}
|
|
21
59
|
}
|
|
22
60
|
|
|
23
61
|
/**
|
|
@@ -30,150 +68,113 @@ export interface ClientState {
|
|
|
30
68
|
* ('project') and the global client ('global'). When set to `'global'`, the
|
|
31
69
|
* global client is used.
|
|
32
70
|
*
|
|
33
|
-
* These options are utilized by `getClient` and `getClientState` to
|
|
34
|
-
* client
|
|
35
|
-
* updates
|
|
71
|
+
* These options are utilized by `getClient` and `getClientState` to configure and
|
|
72
|
+
* return appropriate client instances that automatically handle authentication
|
|
73
|
+
* updates and configuration changes.
|
|
36
74
|
*
|
|
37
75
|
* @public
|
|
38
76
|
*/
|
|
39
|
-
export interface ClientOptions extends ClientConfig {
|
|
77
|
+
export interface ClientOptions extends Pick<ClientConfig, AllowedClientConfigKey>, DatasetHandle {
|
|
40
78
|
/**
|
|
41
|
-
* An optional flag to choose between the
|
|
79
|
+
* An optional flag to choose between the default client (typically project-level)
|
|
42
80
|
* and the global client ('global'). When set to `'global'`, the global client
|
|
43
81
|
* is used.
|
|
44
82
|
*/
|
|
45
|
-
scope?: '
|
|
83
|
+
scope?: 'default' | 'global'
|
|
46
84
|
/**
|
|
47
85
|
* A required string indicating the API version for the client.
|
|
48
86
|
*/
|
|
49
87
|
apiVersion: string
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* A resource identifier for a document, in the format of `projectId.dataset`
|
|
53
|
-
*/
|
|
54
|
-
resourceId?: ResourceId
|
|
55
88
|
}
|
|
56
89
|
|
|
57
|
-
const clientStore
|
|
90
|
+
const clientStore = defineStore<ClientStoreState>({
|
|
58
91
|
name: 'clientStore',
|
|
59
92
|
|
|
60
|
-
getInitialState: (instance
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
dataset: identity.dataset,
|
|
65
|
-
token: config?.auth?.token,
|
|
66
|
-
useCdn: false,
|
|
67
|
-
apiVersion: DEFAULT_API_VERSION,
|
|
68
|
-
requestTagPrefix: DEFAULT_REQUEST_TAG_PREFIX,
|
|
69
|
-
...(config?.auth?.apiHost ? {apiHost: config.auth.apiHost} : {}),
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
const defaultGlobalClient = createClient({
|
|
73
|
-
token: config?.auth?.token,
|
|
74
|
-
useCdn: false,
|
|
75
|
-
apiVersion: 'vX', // Many global APIs are only available under this version, we may need to support other versions in the future
|
|
76
|
-
useProjectHostname: false,
|
|
77
|
-
requestTagPrefix: DEFAULT_REQUEST_TAG_PREFIX,
|
|
78
|
-
...(config?.auth?.apiHost ? {apiHost: config.auth.apiHost} : {}),
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
defaultClient,
|
|
83
|
-
defaultGlobalClient,
|
|
84
|
-
}
|
|
85
|
-
},
|
|
93
|
+
getInitialState: (instance) => ({
|
|
94
|
+
clients: {},
|
|
95
|
+
token: getTokenState(instance).getCurrent(),
|
|
96
|
+
}),
|
|
86
97
|
|
|
87
|
-
initialize() {
|
|
88
|
-
const
|
|
89
|
-
return () =>
|
|
90
|
-
authEventSubscription.unsubscribe()
|
|
91
|
-
}
|
|
98
|
+
initialize(context) {
|
|
99
|
+
const subscription = listenToToken(context)
|
|
100
|
+
return () => subscription.unsubscribe()
|
|
92
101
|
},
|
|
93
102
|
})
|
|
94
103
|
|
|
95
|
-
const receiveToken = (prev: ClientState, token: string | undefined): ClientState => {
|
|
96
|
-
const newDefaultClient = prev.defaultClient.withConfig({
|
|
97
|
-
token,
|
|
98
|
-
})
|
|
99
|
-
const newGlobalClient = prev.defaultGlobalClient.withConfig({
|
|
100
|
-
token,
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
defaultClient: newDefaultClient,
|
|
105
|
-
defaultGlobalClient: newGlobalClient,
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
104
|
/**
|
|
110
105
|
* Updates the client store state when a token is received.
|
|
111
106
|
* @internal
|
|
112
107
|
*/
|
|
113
|
-
const
|
|
114
|
-
(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
const optionsCache = new WeakMap<SanityClient, Map<string, ClientOptions>>()
|
|
124
|
-
|
|
125
|
-
const defaultClientSelector = (state: ClientState, options: ClientOptions) =>
|
|
126
|
-
options?.scope === 'global' ? state.defaultGlobalClient : state.defaultClient
|
|
127
|
-
|
|
128
|
-
const memoizedOptionsSelector = createSelector(
|
|
129
|
-
[defaultClientSelector, (_state: ClientState, options: ClientOptions) => options],
|
|
130
|
-
(client, options) => {
|
|
131
|
-
let nestedCache = optionsCache.get(client)
|
|
132
|
-
if (!nestedCache) {
|
|
133
|
-
nestedCache = new Map<string, ClientOptions>()
|
|
134
|
-
optionsCache.set(client, nestedCache)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const key = JSON.stringify(options)
|
|
138
|
-
const cached = nestedCache.get(key)
|
|
139
|
-
if (cached) return cached
|
|
140
|
-
|
|
141
|
-
nestedCache.set(key, options)
|
|
142
|
-
return options
|
|
143
|
-
},
|
|
144
|
-
)
|
|
108
|
+
const listenToToken = ({instance, state}: StoreContext<ClientStoreState>) => {
|
|
109
|
+
return getTokenState(instance).observable.subscribe((token) => {
|
|
110
|
+
state.set('setTokenAndResetClients', {token, clients: {}})
|
|
111
|
+
})
|
|
112
|
+
}
|
|
145
113
|
|
|
146
|
-
const
|
|
147
|
-
[defaultClientSelector, memoizedOptionsSelector],
|
|
148
|
-
(client, options) => client.withConfig(options),
|
|
149
|
-
)
|
|
114
|
+
const getClientConfigKey = (options: ClientOptions) => JSON.stringify(pick(options, ...allowedKeys))
|
|
150
115
|
|
|
151
116
|
/**
|
|
152
|
-
* Retrieves a
|
|
117
|
+
* Retrieves a Sanity client instance configured with the provided options.
|
|
153
118
|
*
|
|
154
|
-
* This function
|
|
155
|
-
* client
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
119
|
+
* This function returns a client instance configured for the project or as a
|
|
120
|
+
* global client based on the options provided. It ensures efficient reuse of
|
|
121
|
+
* client instances by returning the same instance for the same options.
|
|
122
|
+
* For automatic handling of authentication token updates, consider using
|
|
123
|
+
* `getClientState`.
|
|
159
124
|
*
|
|
160
125
|
* @public
|
|
161
126
|
*/
|
|
162
|
-
export const getClient =
|
|
127
|
+
export const getClient = bindActionGlobally(
|
|
163
128
|
clientStore,
|
|
164
|
-
({state}) =>
|
|
165
|
-
|
|
166
|
-
|
|
129
|
+
({state, instance}, options: ClientOptions) => {
|
|
130
|
+
// Check for disallowed keys
|
|
131
|
+
const providedKeys = Object.keys(options) as (keyof ClientOptions)[]
|
|
132
|
+
const disallowedKeys = providedKeys.filter((key) => !allowedKeys.includes(key))
|
|
133
|
+
|
|
134
|
+
if (disallowedKeys.length > 0) {
|
|
135
|
+
const listFormatter = new Intl.ListFormat('en', {style: 'long', type: 'conjunction'})
|
|
136
|
+
throw new Error(
|
|
137
|
+
`The client options provided contains unsupported properties: ${listFormatter.format(disallowedKeys)}. ` +
|
|
138
|
+
`Allowed keys are: ${listFormatter.format(allowedKeys)}.`,
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const {token, clients} = state.get()
|
|
143
|
+
const projectId = options.projectId ?? instance.config.projectId
|
|
144
|
+
const dataset = options.dataset ?? instance.config.dataset
|
|
145
|
+
const apiHost = options.apiHost ?? instance.config.auth?.apiHost
|
|
146
|
+
|
|
147
|
+
const effectiveOptions: ClientOptions = {
|
|
148
|
+
...DEFAULT_CLIENT_CONFIG,
|
|
149
|
+
...((options.scope === 'global' || !projectId) && {useProjectHostname: false}),
|
|
150
|
+
...(token && {token}),
|
|
151
|
+
...options,
|
|
152
|
+
...(projectId && {projectId}),
|
|
153
|
+
...(dataset && {dataset}),
|
|
154
|
+
...(apiHost && {apiHost}),
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const key = getClientConfigKey(effectiveOptions)
|
|
158
|
+
|
|
159
|
+
if (clients[key]) return clients[key]
|
|
160
|
+
|
|
161
|
+
const client = createClient(effectiveOptions)
|
|
162
|
+
state.set('addClient', (prev) => ({clients: {...prev.clients, [key]: client}}))
|
|
163
|
+
|
|
164
|
+
return client
|
|
165
|
+
},
|
|
167
166
|
)
|
|
168
167
|
|
|
169
168
|
/**
|
|
170
169
|
* Returns a state source for the Sanity client instance.
|
|
171
170
|
*
|
|
172
171
|
* This function provides a subscribable state source that emits updated client
|
|
173
|
-
* instances whenever
|
|
174
|
-
*
|
|
175
|
-
* to ensure that subscribers receive the most current client configuration.
|
|
172
|
+
* instances whenever relevant configurations change (such as authentication tokens).
|
|
173
|
+
* Use this when you need to react to client configuration changes in your application.
|
|
176
174
|
*
|
|
177
175
|
* @public
|
|
178
176
|
*/
|
|
179
|
-
export const getClientState =
|
|
177
|
+
export const getClientState = bindActionGlobally(
|
|
178
|
+
clientStore,
|
|
179
|
+
createStateSourceAction(({instance}, options: ClientOptions) => getClient(instance, options)),
|
|
180
|
+
)
|
|
@@ -1,32 +1,57 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {type Controller} from '@sanity/comlink'
|
|
2
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {type
|
|
6
|
-
import {createResourceState} from '../../../resources/createResource'
|
|
7
|
-
import {comlinkControllerStore} from '../comlinkControllerStore'
|
|
4
|
+
import {createSanityInstance} from '../../../store/createSanityInstance'
|
|
5
|
+
import {createStoreState} from '../../../store/createStoreState'
|
|
6
|
+
import {type ComlinkControllerState} from '../comlinkControllerStore'
|
|
8
7
|
import {destroyController} from './destroyController'
|
|
9
|
-
import {getOrCreateController} from './getOrCreateController'
|
|
10
8
|
|
|
11
9
|
describe('destroyController', () => {
|
|
12
|
-
|
|
10
|
+
const instance = createSanityInstance({
|
|
11
|
+
projectId: 'test-project-id',
|
|
12
|
+
dataset: 'test-dataset',
|
|
13
|
+
})
|
|
14
|
+
let state: ReturnType<typeof createStoreState<ComlinkControllerState>>
|
|
15
|
+
let mockController: {destroy: ReturnType<typeof vi.fn>}
|
|
13
16
|
|
|
14
17
|
beforeEach(() => {
|
|
15
|
-
|
|
18
|
+
mockController = {
|
|
19
|
+
destroy: vi.fn(),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Initialize test store state
|
|
23
|
+
state = createStoreState<ComlinkControllerState>({
|
|
24
|
+
controller: null,
|
|
25
|
+
controllerOrigin: null,
|
|
26
|
+
channels: new Map(),
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
instance.dispose()
|
|
16
32
|
})
|
|
17
33
|
|
|
18
34
|
it('should destroy controller and clear state', () => {
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
// Set up test state with a controller
|
|
36
|
+
state.set('setup', {
|
|
37
|
+
controller: mockController as unknown as Controller,
|
|
38
|
+
controllerOrigin: 'https://test.sanity.dev',
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Execute action
|
|
21
42
|
destroyController({state, instance})
|
|
22
43
|
|
|
44
|
+
// Verify controller was destroyed and state was cleared
|
|
45
|
+
expect(mockController.destroy).toHaveBeenCalled()
|
|
23
46
|
expect(state.get().controller).toBeNull()
|
|
24
47
|
expect(state.get().channels.size).toBe(0)
|
|
25
48
|
})
|
|
26
49
|
|
|
27
50
|
it('should do nothing if no controller exists', () => {
|
|
28
|
-
|
|
29
|
-
|
|
51
|
+
// State already has null controller, so just execute action
|
|
30
52
|
expect(() => destroyController({state, instance})).not.toThrow()
|
|
53
|
+
|
|
54
|
+
// State should remain unchanged
|
|
55
|
+
expect(state.get().controller).toBeNull()
|
|
31
56
|
})
|
|
32
57
|
})
|
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {type StoreContext} from '../../../store/defineStore'
|
|
2
2
|
import {type ComlinkControllerState} from '../comlinkControllerStore'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Calls the destroy method on the controller and resets the controller state.
|
|
6
6
|
* @public
|
|
7
7
|
*/
|
|
8
|
-
export const destroyController =
|
|
9
|
-
|
|
10
|
-
return () => {
|
|
11
|
-
const {controller} = state.get()
|
|
8
|
+
export const destroyController = ({state}: StoreContext<ComlinkControllerState>): void => {
|
|
9
|
+
const {controller} = state.get()
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
},
|
|
22
|
-
)
|
|
11
|
+
if (controller) {
|
|
12
|
+
controller.destroy()
|
|
13
|
+
state.set('destroyController', {
|
|
14
|
+
controller: null,
|
|
15
|
+
channels: new Map(),
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
}
|