@sanity/sdk 0.0.0-alpha.1
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 +339 -0
- package/dist/index.js +492 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
- package/src/_exports/index.ts +39 -0
- package/src/auth/authStore.test.ts +296 -0
- package/src/auth/authStore.ts +125 -0
- package/src/auth/getAuthStore.test.ts +14 -0
- package/src/auth/getInternalAuthStore.ts +20 -0
- package/src/auth/internalAuthStore.test.ts +334 -0
- package/src/auth/internalAuthStore.ts +519 -0
- package/src/client/getClient.test.ts +41 -0
- package/src/client/getClient.ts +13 -0
- package/src/client/getSubscribableClient.test.ts +71 -0
- package/src/client/getSubscribableClient.ts +17 -0
- package/src/client/store/actions/getClientEvents.test.ts +95 -0
- package/src/client/store/actions/getClientEvents.ts +33 -0
- package/src/client/store/actions/getOrCreateClient.test.ts +56 -0
- package/src/client/store/actions/getOrCreateClient.ts +40 -0
- package/src/client/store/actions/receiveToken.test.ts +18 -0
- package/src/client/store/actions/receiveToken.ts +31 -0
- package/src/client/store/clientStore.test.ts +152 -0
- package/src/client/store/clientStore.ts +98 -0
- package/src/documentList/documentListStore.test.ts +575 -0
- package/src/documentList/documentListStore.ts +269 -0
- package/src/documents/.keep +0 -0
- package/src/instance/identity.test.ts +46 -0
- package/src/instance/identity.ts +28 -0
- package/src/instance/sanityInstance.test.ts +66 -0
- package/src/instance/sanityInstance.ts +64 -0
- package/src/instance/types.d.ts +29 -0
- package/src/schema/schemaStore.test.ts +30 -0
- package/src/schema/schemaStore.ts +32 -0
- package/src/store/createStore.test.ts +108 -0
- package/src/store/createStore.ts +106 -0
- package/src/tsdoc.json +39 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {createClient, type SanityClient} from '@sanity/client'
|
|
2
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {config} from '../../../../test/fixtures'
|
|
5
|
+
import {createSanityInstance} from '../../../instance/sanityInstance'
|
|
6
|
+
import {createClientStore} from '../clientStore'
|
|
7
|
+
|
|
8
|
+
describe('getClientEvents', () => {
|
|
9
|
+
const API_VERSION = '2024-12-05'
|
|
10
|
+
let defaultClient: SanityClient
|
|
11
|
+
let store: ReturnType<typeof createClientStore>
|
|
12
|
+
let instance: ReturnType<typeof createSanityInstance>
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
instance = createSanityInstance(config)
|
|
16
|
+
defaultClient = createClient({...config, apiVersion: API_VERSION, useCdn: false})
|
|
17
|
+
store = createClientStore(instance, defaultClient)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('immediately emits initial client', () => {
|
|
21
|
+
const events = store.getClientEvents({apiVersion: '2024-01-01'})
|
|
22
|
+
const mockNext = vi.fn()
|
|
23
|
+
|
|
24
|
+
events.subscribe({next: mockNext})
|
|
25
|
+
|
|
26
|
+
expect(mockNext).toHaveBeenCalledTimes(1)
|
|
27
|
+
const emittedClient = mockNext.mock.calls[0][0]
|
|
28
|
+
expect(emittedClient.config().apiVersion).toBe('2024-01-01')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('emits new client when store updates token', () => {
|
|
32
|
+
const events = store.getClientEvents({apiVersion: '2024-01-01'})
|
|
33
|
+
const mockNext = vi.fn()
|
|
34
|
+
|
|
35
|
+
events.subscribe({next: mockNext})
|
|
36
|
+
store.receiveToken('new-token')
|
|
37
|
+
|
|
38
|
+
expect(mockNext).toHaveBeenCalledTimes(2) // Initial + update
|
|
39
|
+
const latestClient = mockNext.mock.calls[1][0]
|
|
40
|
+
expect(latestClient.config().token).toBe('new-token')
|
|
41
|
+
expect(latestClient.config().apiVersion).toBe('2024-01-01')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('unsubscribes properly', () => {
|
|
45
|
+
const events = store.getClientEvents({apiVersion: '2024-01-01'})
|
|
46
|
+
const mockNext = vi.fn()
|
|
47
|
+
|
|
48
|
+
const subscription = events.subscribe({next: mockNext})
|
|
49
|
+
subscription.unsubscribe()
|
|
50
|
+
|
|
51
|
+
store.receiveToken('new-token')
|
|
52
|
+
|
|
53
|
+
// Should only have the initial emission
|
|
54
|
+
expect(mockNext).toHaveBeenCalledTimes(1)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('throws error when apiVersion is missing', () => {
|
|
58
|
+
expect(() => store.getClientEvents({})).toThrow('Missing required `apiVersion` option')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('maintains apiVersion through updates', () => {
|
|
62
|
+
const events = store.getClientEvents({apiVersion: '2024-01-01'})
|
|
63
|
+
const mockNext = vi.fn()
|
|
64
|
+
|
|
65
|
+
events.subscribe({next: mockNext})
|
|
66
|
+
store.receiveToken('token1')
|
|
67
|
+
store.receiveToken('token2')
|
|
68
|
+
|
|
69
|
+
expect(mockNext).toHaveBeenCalledTimes(3) // Initial + 2 updates
|
|
70
|
+
mockNext.mock.calls.forEach(([client]) => {
|
|
71
|
+
expect(client.config().apiVersion).toBe('2024-01-01')
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('handles multiple subscribers independently', () => {
|
|
76
|
+
const events1 = store.getClientEvents({apiVersion: '2024-01-01'})
|
|
77
|
+
const events2 = store.getClientEvents({apiVersion: '2024-02-01'})
|
|
78
|
+
const mockNext1 = vi.fn()
|
|
79
|
+
const mockNext2 = vi.fn()
|
|
80
|
+
|
|
81
|
+
events1.subscribe({next: mockNext1})
|
|
82
|
+
events2.subscribe({next: mockNext2})
|
|
83
|
+
store.receiveToken('new-token')
|
|
84
|
+
|
|
85
|
+
expect(mockNext1).toHaveBeenCalledTimes(2) // Initial + update
|
|
86
|
+
expect(mockNext2).toHaveBeenCalledTimes(2) // Initial + update
|
|
87
|
+
|
|
88
|
+
const latestClient1 = mockNext1.mock.calls[1][0]
|
|
89
|
+
const latestClient2 = mockNext2.mock.calls[1][0]
|
|
90
|
+
expect(latestClient1.config().apiVersion).toBe('2024-01-01')
|
|
91
|
+
expect(latestClient2.config().apiVersion).toBe('2024-02-01')
|
|
92
|
+
expect(latestClient1.config().token).toBe('new-token')
|
|
93
|
+
expect(latestClient2.config().token).toBe('new-token')
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type {SanityClient} from '@sanity/client'
|
|
2
|
+
import {distinctUntilChanged, map, Observable, startWith, type Subscribable} from 'rxjs'
|
|
3
|
+
|
|
4
|
+
import type {StoreActionContext} from '../../../store/createStore'
|
|
5
|
+
import type {ClientOptions, ClientState} from '../clientStore'
|
|
6
|
+
import {getOrCreateClient} from './getOrCreateClient'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Provides a stream of clients, based on the current state of the store.
|
|
10
|
+
* (For example, when a user logs in, this will emit an authorized client.)
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export const getClientEvents = (
|
|
14
|
+
context: StoreActionContext<ClientState>,
|
|
15
|
+
options: ClientOptions = {},
|
|
16
|
+
): Subscribable<SanityClient> => {
|
|
17
|
+
const {store} = context
|
|
18
|
+
|
|
19
|
+
const initialClient = getOrCreateClient(context, options)
|
|
20
|
+
const clientStore$ = new Observable<void>((subscriber) =>
|
|
21
|
+
store.subscribe(() => subscriber.next()),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const client$ = clientStore$.pipe(
|
|
25
|
+
map(() => getOrCreateClient(context, options)),
|
|
26
|
+
startWith(initialClient),
|
|
27
|
+
distinctUntilChanged((prev, curr) => prev.config().token === curr.config().token),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
subscribe: client$.subscribe.bind(client$),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {createClient, type SanityClient} from '@sanity/client'
|
|
2
|
+
|
|
3
|
+
import {config} from '../../../../test/fixtures'
|
|
4
|
+
import {createSanityInstance} from '../../../instance/sanityInstance'
|
|
5
|
+
import {createClientStore} from '../clientStore'
|
|
6
|
+
|
|
7
|
+
describe('getOrCreateClient', () => {
|
|
8
|
+
const API_VERSION = '2024-12-05'
|
|
9
|
+
let defaultClient: SanityClient
|
|
10
|
+
let store: ReturnType<typeof createClientStore>
|
|
11
|
+
let instance: ReturnType<typeof createSanityInstance>
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
instance = createSanityInstance(config)
|
|
15
|
+
defaultClient = createClient({...config, apiVersion: API_VERSION, useCdn: false})
|
|
16
|
+
store = createClientStore(instance, defaultClient)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('throws error when apiVersion is missing', () => {
|
|
20
|
+
expect(() => store.getOrCreateClient({})).toThrow('Missing required `apiVersion` option')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('creates new client with correct apiVersion', () => {
|
|
24
|
+
const apiVersion = '2024-01-01'
|
|
25
|
+
const result = store.getOrCreateClient({apiVersion})
|
|
26
|
+
expect(result.config().apiVersion).toBe(apiVersion)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('reuses existing client for same apiVersion', () => {
|
|
30
|
+
const apiVersion = '2024-01-01'
|
|
31
|
+
const result1 = store.getOrCreateClient({apiVersion})
|
|
32
|
+
const result2 = store.getOrCreateClient({apiVersion})
|
|
33
|
+
|
|
34
|
+
expect(result1).toBe(result2)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('preserves client identity after token update', () => {
|
|
38
|
+
const apiVersion = '2024-01-01'
|
|
39
|
+
const client1 = store.getOrCreateClient({apiVersion})
|
|
40
|
+
|
|
41
|
+
// Update token
|
|
42
|
+
store.receiveToken('new-token')
|
|
43
|
+
|
|
44
|
+
const client2 = store.getOrCreateClient({apiVersion})
|
|
45
|
+
|
|
46
|
+
// Verify the new token was applied
|
|
47
|
+
expect(client2.config().token).toBe('new-token')
|
|
48
|
+
expect(client2.config().apiVersion).toBe(apiVersion)
|
|
49
|
+
|
|
50
|
+
// Verify we got a new client instance (since token changed)
|
|
51
|
+
expect(client2).not.toBe(client1)
|
|
52
|
+
|
|
53
|
+
// Verify first client keeps its original config
|
|
54
|
+
expect(client1.config().token).toBeUndefined()
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {type SanityClient} from '@sanity/client'
|
|
2
|
+
|
|
3
|
+
import type {StoreActionContext} from '../../../store/createStore'
|
|
4
|
+
import type {ClientOptions, ClientState} from '../clientStore'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Retrieves a memoized client based on the API version,
|
|
8
|
+
* or creates a new one if it doesn't exist.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export const getOrCreateClient = (
|
|
12
|
+
{store}: StoreActionContext<ClientState>,
|
|
13
|
+
options: ClientOptions = {},
|
|
14
|
+
): SanityClient => {
|
|
15
|
+
const state = store.getState()
|
|
16
|
+
const {apiVersion} = options
|
|
17
|
+
|
|
18
|
+
if (!apiVersion) {
|
|
19
|
+
throw new Error('Missing required `apiVersion` option')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const cached = state.clients.get(apiVersion)
|
|
23
|
+
if (cached) {
|
|
24
|
+
return cached
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Create new client with specified API version
|
|
28
|
+
const client = state.defaultClient.withConfig(options)
|
|
29
|
+
|
|
30
|
+
// Update state with new client
|
|
31
|
+
store.setState((prevState) => {
|
|
32
|
+
const newMap = new Map(prevState.clients)
|
|
33
|
+
newMap.set(apiVersion, client)
|
|
34
|
+
return {
|
|
35
|
+
clients: newMap,
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return client
|
|
40
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {config} from '../../../../test/fixtures'
|
|
2
|
+
import {createSanityInstance} from '../../../instance/sanityInstance'
|
|
3
|
+
import {getClientStore} from '../clientStore'
|
|
4
|
+
|
|
5
|
+
describe('receiveToken', () => {
|
|
6
|
+
const sanityInstance = createSanityInstance(config)
|
|
7
|
+
it('updates client tokens when auth state changes', () => {
|
|
8
|
+
const store = getClientStore(sanityInstance)
|
|
9
|
+
|
|
10
|
+
const client = store.getOrCreateClient({apiVersion: 'v2023-01-01'})
|
|
11
|
+
expect(client.config().token).toBeUndefined()
|
|
12
|
+
|
|
13
|
+
store.receiveToken('new-token')
|
|
14
|
+
|
|
15
|
+
const updatedClient = store.getOrCreateClient({apiVersion: 'v2023-01-01'})
|
|
16
|
+
expect(updatedClient.config().token).toBe('new-token')
|
|
17
|
+
})
|
|
18
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type {StoreActionContext} from '../../../store/createStore'
|
|
2
|
+
import type {ClientState} from '../clientStore'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Updates the client store state when a token is received.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export const receiveToken = (
|
|
9
|
+
{store}: StoreActionContext<ClientState>,
|
|
10
|
+
token: string | undefined,
|
|
11
|
+
): void => {
|
|
12
|
+
// Update the default client
|
|
13
|
+
const newDefaultClient = store.getState().defaultClient.withConfig({
|
|
14
|
+
token,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// Update existing clients while preserving the map structure
|
|
18
|
+
store.setState((prevState) => {
|
|
19
|
+
const updatedClients = new Map(
|
|
20
|
+
Array.from(prevState.clients.entries()).map(([version, client]) => [
|
|
21
|
+
version,
|
|
22
|
+
client.withConfig({token}),
|
|
23
|
+
]),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
defaultClient: newDefaultClient,
|
|
28
|
+
clients: updatedClients,
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {config} from '../../../test/fixtures'
|
|
4
|
+
import {getInternalAuthStore} from '../../auth/getInternalAuthStore'
|
|
5
|
+
import {createSanityInstance} from '../../instance/sanityInstance'
|
|
6
|
+
import {getClientStore} from './clientStore'
|
|
7
|
+
|
|
8
|
+
// Mock at module level but don't provide implementation yet
|
|
9
|
+
vi.mock('../../auth/getAuthStore')
|
|
10
|
+
|
|
11
|
+
describe.skip('clientStore', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetModules()
|
|
14
|
+
vi.clearAllMocks()
|
|
15
|
+
// Reset to default mock implementation
|
|
16
|
+
vi.mocked(getInternalAuthStore).mockImplementation(() => ({
|
|
17
|
+
setState: vi.fn(),
|
|
18
|
+
getState: () => ({
|
|
19
|
+
authState: {type: 'logged-out', isDestroyingSession: false},
|
|
20
|
+
providers: undefined,
|
|
21
|
+
setAuthState: vi.fn(),
|
|
22
|
+
setProviders: vi.fn(),
|
|
23
|
+
handleCallback: vi.fn(),
|
|
24
|
+
getLoginUrls: vi.fn(),
|
|
25
|
+
logout: vi.fn(),
|
|
26
|
+
dispose: vi.fn(),
|
|
27
|
+
}),
|
|
28
|
+
getInitialState: () => ({
|
|
29
|
+
authState: {type: 'logged-out', isDestroyingSession: false},
|
|
30
|
+
providers: undefined,
|
|
31
|
+
setAuthState: vi.fn(),
|
|
32
|
+
setProviders: vi.fn(),
|
|
33
|
+
handleCallback: vi.fn(),
|
|
34
|
+
getLoginUrls: vi.fn(),
|
|
35
|
+
logout: vi.fn(),
|
|
36
|
+
dispose: vi.fn(),
|
|
37
|
+
}),
|
|
38
|
+
subscribe: () => () => {}, // Default no-op implementation
|
|
39
|
+
}))
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('creates a store with the expected interface', () => {
|
|
43
|
+
const sanityInstance = createSanityInstance(config)
|
|
44
|
+
const store = getClientStore(sanityInstance)
|
|
45
|
+
expect(store).toHaveProperty('getClientEvents')
|
|
46
|
+
expect(store).toHaveProperty('getOrCreateClient')
|
|
47
|
+
expect(store).toHaveProperty('receiveToken')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('provides clients with correct configuration', () => {
|
|
51
|
+
const sanityInstance = createSanityInstance(config)
|
|
52
|
+
const store = getClientStore(sanityInstance)
|
|
53
|
+
const client = store.getOrCreateClient({apiVersion: 'v2024-11-12'})
|
|
54
|
+
const clientConfig = client.config()
|
|
55
|
+
expect(clientConfig).toMatchObject({
|
|
56
|
+
...config,
|
|
57
|
+
useCdn: false,
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('creates clients with config auth token', () => {
|
|
62
|
+
const instanceWithToken = createSanityInstance({
|
|
63
|
+
...config,
|
|
64
|
+
auth: {
|
|
65
|
+
token: 'initial-auth-token',
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const store = getClientStore(instanceWithToken)
|
|
70
|
+
const client = store.getOrCreateClient({apiVersion: 'v2024-11-12'})
|
|
71
|
+
|
|
72
|
+
expect(client.config().token).toBe('initial-auth-token')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('handles logged-in auth state changes', async () => {
|
|
76
|
+
const sanityInstance = createSanityInstance(config)
|
|
77
|
+
|
|
78
|
+
// Override mock implementation just for this test
|
|
79
|
+
vi.mocked(getInternalAuthStore).mockImplementation(() => ({
|
|
80
|
+
setState: vi.fn(),
|
|
81
|
+
getState: () => ({
|
|
82
|
+
authState: {type: 'logged-in', token: 'test-token', currentUser: null},
|
|
83
|
+
providers: undefined,
|
|
84
|
+
setAuthState: vi.fn(),
|
|
85
|
+
setProviders: vi.fn(),
|
|
86
|
+
handleCallback: vi.fn(),
|
|
87
|
+
getLoginUrls: vi.fn(),
|
|
88
|
+
logout: vi.fn(),
|
|
89
|
+
dispose: vi.fn(),
|
|
90
|
+
}),
|
|
91
|
+
getInitialState: () => ({
|
|
92
|
+
authState: {type: 'logged-in', token: 'test-token', currentUser: null},
|
|
93
|
+
providers: undefined,
|
|
94
|
+
setAuthState: vi.fn(),
|
|
95
|
+
setProviders: vi.fn(),
|
|
96
|
+
handleCallback: vi.fn(),
|
|
97
|
+
getLoginUrls: vi.fn(),
|
|
98
|
+
logout: vi.fn(),
|
|
99
|
+
dispose: vi.fn(),
|
|
100
|
+
}),
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
+
subscribe: (observer: any) => {
|
|
103
|
+
observer.next({type: 'logged-in', token: 'test-token', currentUser: null})
|
|
104
|
+
return () => {}
|
|
105
|
+
},
|
|
106
|
+
}))
|
|
107
|
+
|
|
108
|
+
const store = getClientStore(sanityInstance)
|
|
109
|
+
|
|
110
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
111
|
+
|
|
112
|
+
const client = store.getOrCreateClient({apiVersion: 'v2023-01-01'})
|
|
113
|
+
expect(client.config().token).toBe('test-token')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('properly cleans up auth subscription when cleanup is called', () => {
|
|
117
|
+
const unsubscribeSpy = vi.fn()
|
|
118
|
+
|
|
119
|
+
// Mock the auth store with a spy on the unsubscribe function
|
|
120
|
+
vi.mocked(getInternalAuthStore).mockImplementation(() => ({
|
|
121
|
+
setState: vi.fn(),
|
|
122
|
+
getState: () => ({
|
|
123
|
+
authState: {type: 'logged-in', token: 'test-token', currentUser: null},
|
|
124
|
+
providers: undefined,
|
|
125
|
+
setAuthState: vi.fn(),
|
|
126
|
+
setProviders: vi.fn(),
|
|
127
|
+
handleCallback: vi.fn(),
|
|
128
|
+
getLoginUrls: vi.fn(),
|
|
129
|
+
logout: vi.fn(),
|
|
130
|
+
dispose: vi.fn(),
|
|
131
|
+
}),
|
|
132
|
+
getInitialState: () => ({
|
|
133
|
+
authState: {type: 'logged-in', token: 'test-token', currentUser: null},
|
|
134
|
+
providers: undefined,
|
|
135
|
+
setAuthState: vi.fn(),
|
|
136
|
+
setProviders: vi.fn(),
|
|
137
|
+
handleCallback: vi.fn(),
|
|
138
|
+
getLoginUrls: vi.fn(),
|
|
139
|
+
logout: vi.fn(),
|
|
140
|
+
dispose: vi.fn(),
|
|
141
|
+
}),
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
143
|
+
subscribe: (observer: any) => {
|
|
144
|
+
observer.next({type: 'logged-in', token: 'test-token', currentUser: null})
|
|
145
|
+
return unsubscribeSpy
|
|
146
|
+
},
|
|
147
|
+
}))
|
|
148
|
+
|
|
149
|
+
// Verify that the unsubscribe function was called
|
|
150
|
+
expect(unsubscribeSpy).toHaveBeenCalledTimes(1)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {createClient, type SanityClient} from '@sanity/client'
|
|
2
|
+
import type {Subscribable} from 'rxjs'
|
|
3
|
+
|
|
4
|
+
import {getInternalAuthStore} from '../../auth/getInternalAuthStore'
|
|
5
|
+
import {getOrCreateResource} from '../../instance/sanityInstance'
|
|
6
|
+
import type {SanityInstance} from '../../instance/types'
|
|
7
|
+
import {createStore} from '../../store/createStore'
|
|
8
|
+
import {getClientEvents} from './actions/getClientEvents'
|
|
9
|
+
import {getOrCreateClient} from './actions/getOrCreateClient'
|
|
10
|
+
import {receiveToken} from './actions/receiveToken'
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_API_VERSION = 'v2024-11-12'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Options used when retrieving a client via getOrCreateClient.
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export interface ClientOptions {
|
|
19
|
+
apiVersion?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Internal state of the client store.
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export interface ClientState {
|
|
27
|
+
// default client shouldn't be exposed, but is used in creation of new clients
|
|
28
|
+
defaultClient: SanityClient
|
|
29
|
+
clients: Map<string, SanityClient>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Collection of actions to retrieve or create clients.
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export interface ClientStore {
|
|
37
|
+
getOrCreateClient: (options: ClientOptions) => SanityClient
|
|
38
|
+
receiveToken: (token: string | undefined) => void
|
|
39
|
+
getClientEvents: (options: ClientOptions) => Subscribable<SanityClient>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const createInitialState = (defaultClient: SanityClient): ClientState => {
|
|
43
|
+
const clients = new Map<string, SanityClient>()
|
|
44
|
+
clients.set(defaultClient.config().apiVersion, defaultClient)
|
|
45
|
+
return {defaultClient, clients}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const clientStoreActions = {
|
|
49
|
+
getOrCreateClient,
|
|
50
|
+
receiveToken,
|
|
51
|
+
getClientEvents,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Construction method for creating a client store, including subscribing to auth store.
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
export const createClientStore = (
|
|
59
|
+
instance: SanityInstance,
|
|
60
|
+
defaultClient: SanityClient,
|
|
61
|
+
): ClientStore => {
|
|
62
|
+
const internalAuthStore = getInternalAuthStore(instance)
|
|
63
|
+
|
|
64
|
+
const store = createStore(createInitialState(defaultClient), clientStoreActions, {
|
|
65
|
+
name: 'clientStore',
|
|
66
|
+
instance,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
internalAuthStore.subscribe((state, prevState) => {
|
|
70
|
+
if (state.authState.type === 'logged-in' && prevState.authState.type !== 'logged-in') {
|
|
71
|
+
store.receiveToken(state.authState.token)
|
|
72
|
+
}
|
|
73
|
+
if (prevState.authState.type === 'logged-in' && state.authState.type !== 'logged-in') {
|
|
74
|
+
store.receiveToken(undefined)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
return store
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* This is an internal function that retrieves or creates a client store.
|
|
83
|
+
* @internal
|
|
84
|
+
*/
|
|
85
|
+
export const getClientStore = (instance: SanityInstance): ClientStore => {
|
|
86
|
+
return getOrCreateResource(instance, 'clientStore', () => {
|
|
87
|
+
const {config, identity} = instance
|
|
88
|
+
|
|
89
|
+
const client = createClient({
|
|
90
|
+
projectId: identity.projectId,
|
|
91
|
+
dataset: identity.dataset,
|
|
92
|
+
token: config?.auth?.token,
|
|
93
|
+
useCdn: false,
|
|
94
|
+
apiVersion: DEFAULT_API_VERSION,
|
|
95
|
+
})
|
|
96
|
+
return createClientStore(instance, client)
|
|
97
|
+
})
|
|
98
|
+
}
|