@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,334 @@
|
|
|
1
|
+
import type {CurrentUser} from '@sanity/types'
|
|
2
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {getInternalAuthStore} from './getInternalAuthStore'
|
|
5
|
+
import {createInternalAuthStore} from './internalAuthStore'
|
|
6
|
+
|
|
7
|
+
// Define mockClient before vi.mock
|
|
8
|
+
const mockClient = {
|
|
9
|
+
request: vi.fn().mockResolvedValue({}),
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Move mock before any usage
|
|
13
|
+
vi.mock('@sanity/client', () => ({
|
|
14
|
+
createClient: () => mockClient,
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
const instance = {
|
|
18
|
+
identity: {
|
|
19
|
+
id: '123abcStore',
|
|
20
|
+
projectId: 'test-project',
|
|
21
|
+
dataset: 'test-dataset',
|
|
22
|
+
},
|
|
23
|
+
config: {},
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
const mockStorage = {
|
|
27
|
+
getItem: vi.fn(),
|
|
28
|
+
setItem: vi.fn(),
|
|
29
|
+
removeItem: vi.fn(),
|
|
30
|
+
length: 0,
|
|
31
|
+
clear: vi.fn(),
|
|
32
|
+
key: vi.fn(),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Add this mock setup at the top with other mocks
|
|
36
|
+
const mockWindow = {
|
|
37
|
+
localStorage: mockStorage,
|
|
38
|
+
addEventListener: vi.fn(),
|
|
39
|
+
removeEventListener: vi.fn(),
|
|
40
|
+
dispatchEvent: vi.fn(),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class MockStorageEvent {
|
|
44
|
+
key: string | null
|
|
45
|
+
newValue: string | null
|
|
46
|
+
storageArea: Storage | null
|
|
47
|
+
|
|
48
|
+
constructor(_type: string, eventInit?: StorageEventInit) {
|
|
49
|
+
this.key = eventInit?.key ?? null
|
|
50
|
+
this.newValue = eventInit?.newValue ?? null
|
|
51
|
+
this.storageArea = eventInit?.storageArea ?? null
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Mock StorageEvent globally
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
|
+
global.StorageEvent = MockStorageEvent as any
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
vi.clearAllMocks()
|
|
61
|
+
mockStorage.getItem.mockReturnValue(null)
|
|
62
|
+
// Set up window mock for each test
|
|
63
|
+
global.window = mockWindow as unknown as Window & typeof globalThis
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
// Clean up window mock after each test
|
|
68
|
+
global.window = undefined as unknown as Window & typeof globalThis
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('createInternalAuthStore', () => {
|
|
72
|
+
it('initializes with logged-out state when no token is present', () => {
|
|
73
|
+
const store = createInternalAuthStore(instance, {storageArea: mockStorage})
|
|
74
|
+
expect(store.getState().authState).toEqual({type: 'logged-out', isDestroyingSession: false})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('initializes with logged-in state when token is provided', () => {
|
|
78
|
+
const store = createInternalAuthStore(instance, {
|
|
79
|
+
token: 'static-token',
|
|
80
|
+
storageArea: mockStorage,
|
|
81
|
+
})
|
|
82
|
+
expect(store.getState().authState).toEqual({
|
|
83
|
+
type: 'logged-in',
|
|
84
|
+
token: 'static-token',
|
|
85
|
+
currentUser: null,
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('initializes with logged-in state and defaults to local storage', () => {
|
|
90
|
+
global.window = mockWindow as unknown as typeof window
|
|
91
|
+
|
|
92
|
+
const store = createInternalAuthStore(instance, {
|
|
93
|
+
token: 'static-token',
|
|
94
|
+
})
|
|
95
|
+
expect(store.getState().authState).toEqual({
|
|
96
|
+
type: 'logged-in',
|
|
97
|
+
token: 'static-token',
|
|
98
|
+
currentUser: null,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Cleanup
|
|
102
|
+
global.window = undefined as unknown as Window & typeof globalThis
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('handles successful login callback', async () => {
|
|
106
|
+
mockClient.request.mockResolvedValueOnce({token: 'new-token'})
|
|
107
|
+
|
|
108
|
+
const store = createInternalAuthStore(instance, {
|
|
109
|
+
storageArea: mockStorage,
|
|
110
|
+
initialLocationHref: 'http://localhost/#sid=callback-code',
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const result = await store
|
|
114
|
+
.getState()
|
|
115
|
+
.handleCallback(new URL('http://localhost/#sid=callback-code').href)
|
|
116
|
+
expect(result).toBe('http://localhost/')
|
|
117
|
+
expect(mockStorage.setItem).toHaveBeenCalledWith(
|
|
118
|
+
'__sanity_auth_token_test-project_test-dataset',
|
|
119
|
+
JSON.stringify({token: 'new-token'}),
|
|
120
|
+
)
|
|
121
|
+
expect(store.getState().authState).toEqual({
|
|
122
|
+
type: 'logged-in',
|
|
123
|
+
token: 'new-token',
|
|
124
|
+
currentUser: {},
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('fetches login URLs', async () => {
|
|
129
|
+
const mockProviders = [
|
|
130
|
+
{name: 'google', title: 'Google', url: 'http://api.sanity.io/auth/google'},
|
|
131
|
+
]
|
|
132
|
+
mockClient.request.mockResolvedValueOnce({providers: mockProviders})
|
|
133
|
+
|
|
134
|
+
const store = createInternalAuthStore(instance, {storageArea: mockStorage})
|
|
135
|
+
const providers = await store.getState().getLoginUrls()
|
|
136
|
+
|
|
137
|
+
expect(providers[0]).toMatchObject({
|
|
138
|
+
name: 'google',
|
|
139
|
+
title: 'Google',
|
|
140
|
+
url: expect.stringContaining('http://api.sanity.io/auth/google'),
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('handles logout', async () => {
|
|
145
|
+
mockStorage.getItem.mockReturnValueOnce(JSON.stringify({token: 'stored-token'}))
|
|
146
|
+
const store = createInternalAuthStore(instance, {storageArea: mockStorage})
|
|
147
|
+
|
|
148
|
+
await store.getState().logout()
|
|
149
|
+
|
|
150
|
+
expect(mockClient.request).toHaveBeenCalledWith({
|
|
151
|
+
uri: '/auth/logout',
|
|
152
|
+
method: 'POST',
|
|
153
|
+
})
|
|
154
|
+
expect(mockStorage.removeItem).toHaveBeenCalledWith(
|
|
155
|
+
'__sanity_auth_token_test-project_test-dataset',
|
|
156
|
+
)
|
|
157
|
+
expect(store.getState().authState).toEqual({
|
|
158
|
+
type: 'logged-out',
|
|
159
|
+
isDestroyingSession: false,
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('fetches current user after login', async () => {
|
|
164
|
+
const mockUser: CurrentUser = {
|
|
165
|
+
id: 'user-123',
|
|
166
|
+
name: 'Test User',
|
|
167
|
+
email: 'test@example.com',
|
|
168
|
+
role: 'developer',
|
|
169
|
+
roles: [{name: 'developer', title: 'Developer'}],
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
mockClient.request.mockResolvedValueOnce({token: 'new-token'}).mockResolvedValueOnce(mockUser)
|
|
173
|
+
|
|
174
|
+
const store = createInternalAuthStore(instance, {
|
|
175
|
+
storageArea: mockStorage,
|
|
176
|
+
initialLocationHref: 'http://localhost/#sid=callback-code',
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
await store.getState().handleCallback(new URL('http://localhost/#sid=callback-code').href)
|
|
180
|
+
|
|
181
|
+
expect(mockClient.request).toHaveBeenCalledWith({
|
|
182
|
+
uri: '/users/me',
|
|
183
|
+
method: 'GET',
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
expect(store.getState().authState).toEqual({
|
|
187
|
+
type: 'logged-in',
|
|
188
|
+
token: 'new-token',
|
|
189
|
+
currentUser: mockUser,
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('handles error during token exchange', async () => {
|
|
194
|
+
mockClient.request.mockRejectedValueOnce(new Error('Token exchange failed'))
|
|
195
|
+
|
|
196
|
+
const store = createInternalAuthStore(instance, {
|
|
197
|
+
storageArea: mockStorage,
|
|
198
|
+
initialLocationHref: 'http://localhost/#sid=callback-code',
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const result = await store
|
|
202
|
+
.getState()
|
|
203
|
+
.handleCallback(new URL('http://localhost/#sid=callback-code').href)
|
|
204
|
+
|
|
205
|
+
expect(result).toBe(false)
|
|
206
|
+
expect(store.getState().authState).toEqual({
|
|
207
|
+
type: 'error',
|
|
208
|
+
error: new Error('Token exchange failed'),
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('handles error during user fetch', async () => {
|
|
213
|
+
mockClient.request
|
|
214
|
+
.mockResolvedValueOnce({token: 'new-token'})
|
|
215
|
+
.mockRejectedValueOnce(new Error('User fetch failed'))
|
|
216
|
+
|
|
217
|
+
const store = createInternalAuthStore(instance, {
|
|
218
|
+
storageArea: mockStorage,
|
|
219
|
+
initialLocationHref: 'http://localhost/#sid=callback-code',
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
await store.getState().handleCallback(new URL('http://localhost/#sid=callback-code').href)
|
|
223
|
+
|
|
224
|
+
// Add a small delay to allow the async user fetch to complete
|
|
225
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
226
|
+
|
|
227
|
+
expect(store.getState().authState).toEqual({
|
|
228
|
+
type: 'error',
|
|
229
|
+
token: 'new-token',
|
|
230
|
+
currentUser: null,
|
|
231
|
+
error: new Error('User fetch failed'),
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('handles custom providers function', async () => {
|
|
236
|
+
const customProvider = {
|
|
237
|
+
name: 'custom',
|
|
238
|
+
title: 'Custom',
|
|
239
|
+
url: 'http://custom.auth',
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
mockClient.request.mockResolvedValueOnce({
|
|
243
|
+
providers: [{name: 'google', title: 'Google', url: 'http://api.sanity.io/auth/google'}],
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
const store = createInternalAuthStore(instance, {
|
|
247
|
+
storageArea: mockStorage,
|
|
248
|
+
providers: (defaultProviders) => [...defaultProviders, customProvider],
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const providers = await store.getState().getLoginUrls()
|
|
252
|
+
|
|
253
|
+
expect(providers).toHaveLength(2)
|
|
254
|
+
expect(providers[1]).toMatchObject({
|
|
255
|
+
name: 'custom',
|
|
256
|
+
title: 'Custom',
|
|
257
|
+
url: expect.stringContaining('http://custom.auth'),
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('handles logout during in-flight logout', async () => {
|
|
262
|
+
mockStorage.getItem.mockReturnValueOnce(JSON.stringify({token: 'stored-token'}))
|
|
263
|
+
const store = createInternalAuthStore(instance, {storageArea: mockStorage})
|
|
264
|
+
|
|
265
|
+
// Start first logout
|
|
266
|
+
const firstLogout = store.getState().logout()
|
|
267
|
+
// Attempt second logout before first completes
|
|
268
|
+
const secondLogout = store.getState().logout()
|
|
269
|
+
|
|
270
|
+
await Promise.all([firstLogout, secondLogout])
|
|
271
|
+
|
|
272
|
+
// Should only make one logout request
|
|
273
|
+
expect(mockClient.request).toHaveBeenCalledWith({
|
|
274
|
+
uri: '/auth/logout',
|
|
275
|
+
method: 'POST',
|
|
276
|
+
})
|
|
277
|
+
expect(
|
|
278
|
+
mockClient.request.mock.calls.filter((call) => call[0].uri === '/auth/logout'),
|
|
279
|
+
).toHaveLength(1)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('handles storage events', () => {
|
|
283
|
+
const store = createInternalAuthStore(instance, {
|
|
284
|
+
storageArea: mockStorage,
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
// Mock the storage to return the new token when checked
|
|
288
|
+
mockStorage.getItem.mockReturnValue(JSON.stringify({token: 'new-token'}))
|
|
289
|
+
|
|
290
|
+
// Simulate storage event
|
|
291
|
+
const storageEvent = new StorageEvent('storage', {
|
|
292
|
+
key: '__sanity_auth_token_test-project_test-dataset',
|
|
293
|
+
newValue: JSON.stringify({token: 'new-token'}),
|
|
294
|
+
storageArea: mockStorage,
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
// Get the storage event handler
|
|
298
|
+
const [[eventName, handler]] = mockWindow.addEventListener.mock.calls
|
|
299
|
+
expect(eventName).toBe('storage')
|
|
300
|
+
|
|
301
|
+
// Manually call the handler
|
|
302
|
+
handler(storageEvent)
|
|
303
|
+
|
|
304
|
+
expect(store.getState().authState).toEqual({
|
|
305
|
+
type: 'logged-in',
|
|
306
|
+
token: 'new-token',
|
|
307
|
+
currentUser: null,
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
describe('getInternalAuthStore', () => {
|
|
313
|
+
it('returns the same store instance for the same sanity instance', () => {
|
|
314
|
+
const store1 = getInternalAuthStore(instance)
|
|
315
|
+
const store2 = getInternalAuthStore(instance)
|
|
316
|
+
|
|
317
|
+
expect(store1).toBe(store2)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('creates different stores for different instances', () => {
|
|
321
|
+
const instance2 = {
|
|
322
|
+
...instance,
|
|
323
|
+
identity: {
|
|
324
|
+
...instance.identity,
|
|
325
|
+
id: 'different-id',
|
|
326
|
+
},
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const store1 = getInternalAuthStore(instance)
|
|
330
|
+
const store2 = getInternalAuthStore(instance2)
|
|
331
|
+
|
|
332
|
+
expect(store1).not.toBe(store2)
|
|
333
|
+
})
|
|
334
|
+
})
|