Skip to content

Commit 1c97ac7

Browse files
committed
feat: support using the same instance for different datasets
This introduces the concept of a "key" for a store. The main goal here is to enable the support for us using a single SanityInstance across all the stores, but still have separate stores per projectId/dataset. This is accomplished by saying that each store is determined by a "key" which can be dynamically built from the options. We first invoke the key logic and then dispatches to the right store. For now have projectId/dataset keys, but in the future we expect this to be per-resource. This also means that it's possible to use a single SanityInstance for working with multiple projectId/dataset. If you pass in an explicit projectId/dataset then it will no longer care about the configuration on the SanityInstance.
1 parent 2cbcac0 commit 1c97ac7

20 files changed

+174
-102
lines changed

packages/core/src/auth/authStore.test.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ describe('authStore', () => {
9292
},
9393
})
9494

95-
const {options, dashboardContext} = authStore.getInitialState(instance)
95+
const {options, dashboardContext} = authStore.getInitialState(instance, null)
9696

9797
expect(options.apiHost).toBe(apiHost)
9898
expect(options.callbackUrl).toBe(callbackUrl)
@@ -114,7 +114,7 @@ describe('authStore', () => {
114114
auth: {initialLocationHref},
115115
})
116116

117-
const {dashboardContext, authState} = authStore.getInitialState(instance)
117+
const {dashboardContext, authState} = authStore.getInitialState(instance, null)
118118
expect(dashboardContext).toEqual(context)
119119
expect(authState.type).toBe(AuthStateType.LOGGED_OUT)
120120
})
@@ -129,7 +129,7 @@ describe('authStore', () => {
129129
auth: {initialLocationHref},
130130
})
131131

132-
const {dashboardContext, authState} = authStore.getInitialState(instance)
132+
const {dashboardContext, authState} = authStore.getInitialState(instance, null)
133133
expect(dashboardContext).toEqual(expectedContext)
134134
expect(authState.type).toBe(AuthStateType.LOGGED_OUT)
135135
})
@@ -143,7 +143,7 @@ describe('authStore', () => {
143143
auth: {initialLocationHref},
144144
})
145145

146-
const {dashboardContext, authState} = authStore.getInitialState(instance)
146+
const {dashboardContext, authState} = authStore.getInitialState(instance, null)
147147
expect(dashboardContext).toStrictEqual({})
148148
expect(authState.type).toBe(AuthStateType.LOGGED_OUT)
149149
expect(errorSpy).toHaveBeenCalledWith(
@@ -166,7 +166,7 @@ describe('authStore', () => {
166166
},
167167
})
168168

169-
const {authState, dashboardContext} = authStore.getInitialState(instance)
169+
const {authState, dashboardContext} = authStore.getInitialState(instance, null)
170170
expect(authState).toMatchObject({type: AuthStateType.LOGGED_IN, token})
171171
expect(dashboardContext).toEqual(context)
172172
})
@@ -182,7 +182,7 @@ describe('authStore', () => {
182182

183183
vi.mocked(getAuthCode).mockReturnValue('auth-code')
184184

185-
const {authState, dashboardContext} = authStore.getInitialState(instance)
185+
const {authState, dashboardContext} = authStore.getInitialState(instance, null)
186186
expect(authState).toMatchObject({type: AuthStateType.LOGGING_IN})
187187
expect(dashboardContext).toEqual(context)
188188
})
@@ -197,7 +197,7 @@ describe('authStore', () => {
197197
vi.mocked(getAuthCode).mockReturnValue(null)
198198
vi.mocked(getTokenFromStorage).mockReturnValue(storageToken)
199199

200-
const {authState, dashboardContext} = authStore.getInitialState(instance)
200+
const {authState, dashboardContext} = authStore.getInitialState(instance, null)
201201
expect(authState).toMatchObject({type: AuthStateType.LOGGED_IN, token: storageToken})
202202
expect(dashboardContext).toStrictEqual({})
203203
})
@@ -215,7 +215,7 @@ describe('authStore', () => {
215215
vi.mocked(getAuthCode).mockReturnValue(null)
216216
vi.mocked(getTokenFromStorage).mockReturnValue(storageToken)
217217

218-
const {authState, dashboardContext} = authStore.getInitialState(instance)
218+
const {authState, dashboardContext} = authStore.getInitialState(instance, null)
219219
expect(authState).toMatchObject({type: AuthStateType.LOGGED_OUT})
220220
expect(dashboardContext).toEqual(context)
221221
})
@@ -229,7 +229,7 @@ describe('authStore', () => {
229229
vi.mocked(getAuthCode).mockReturnValue(null)
230230
vi.mocked(getTokenFromStorage).mockReturnValue(null)
231231

232-
const {authState, dashboardContext} = authStore.getInitialState(instance)
232+
const {authState, dashboardContext} = authStore.getInitialState(instance, null)
233233
expect(authState).toMatchObject({type: AuthStateType.LOGGED_OUT})
234234
expect(dashboardContext).toStrictEqual({})
235235
})
@@ -251,7 +251,7 @@ describe('authStore', () => {
251251
auth: {storageArea: mockStorage}, // Provide mock storage
252252
})
253253

254-
const {authState, options} = authStore.getInitialState(instance)
254+
const {authState, options} = authStore.getInitialState(instance, null)
255255
expect(getStudioTokenFromLocalStorage).toHaveBeenCalledWith(mockStorage, projectId)
256256
expect(authState).toMatchObject({type: AuthStateType.LOGGED_IN, token: studioToken})
257257
expect(options.authMethod).toBe('localstorage')
@@ -277,7 +277,7 @@ describe('authStore', () => {
277277
})
278278

279279
// Initial state might be logged out before the async check completes
280-
const {authState: initialAuthState} = authStore.getInitialState(instance)
280+
const {authState: initialAuthState} = authStore.getInitialState(instance, null)
281281
expect(initialAuthState.type).toBe(AuthStateType.LOGGED_OUT) // Or potentially logging in depending on other factors
282282
expect(getStudioTokenFromLocalStorage).toHaveBeenCalledWith(mockStorage, projectId)
283283
expect(checkForCookieAuth).toHaveBeenCalledWith(projectId, expect.any(Function))
@@ -297,7 +297,7 @@ describe('authStore', () => {
297297
dataset: 'd',
298298
})
299299

300-
const {authState, options} = authStore.getInitialState(instance)
300+
const {authState, options} = authStore.getInitialState(instance, null)
301301
expect(getStudioTokenFromLocalStorage).not.toHaveBeenCalled()
302302
expect(checkForCookieAuth).not.toHaveBeenCalled()
303303
expect(getTokenFromStorage).toHaveBeenCalled()
@@ -315,7 +315,7 @@ describe('authStore', () => {
315315
vi.mocked(getAuthCode).mockReturnValue(null)
316316
vi.mocked(getTokenFromLocation).mockReturnValue('hash-token')
317317

318-
const {authState} = authStore.getInitialState(instance)
318+
const {authState} = authStore.getInitialState(instance, null)
319319
expect(authState).toMatchObject({
320320
type: AuthStateType.LOGGING_IN,
321321
isExchangingToken: false,

packages/core/src/auth/refreshStampedToken.test.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ describe('refreshStampedToken', () => {
122122
dataset: 'd',
123123
auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
124124
})
125-
const initialState = authStore.getInitialState(instance)
125+
const initialState = authStore.getInitialState(instance, null)
126126
initialState.authState = {
127127
type: AuthStateType.LOGGED_IN,
128128
token: 'sk-initial-token-st123',
@@ -131,7 +131,7 @@ describe('refreshStampedToken', () => {
131131
initialState.dashboardContext = {mode: 'test'}
132132
const state = createStoreState(initialState)
133133

134-
const subscription = refreshStampedToken({state, instance})
134+
const subscription = refreshStampedToken({state, instance, key: null})
135135
subscriptions.push(subscription)
136136

137137
await vi.advanceTimersToNextTimerAsync()
@@ -168,7 +168,7 @@ describe('refreshStampedToken', () => {
168168
dataset: 'd',
169169
auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
170170
})
171-
const initialState = authStore.getInitialState(instance)
171+
const initialState = authStore.getInitialState(instance, null)
172172
initialState.authState = {
173173
type: AuthStateType.LOGGED_IN,
174174
token: 'sk-initial-token-st123',
@@ -177,7 +177,7 @@ describe('refreshStampedToken', () => {
177177
initialState.dashboardContext = {mode: 'test'}
178178
const state = createStoreState(initialState)
179179

180-
const subscription = refreshStampedToken({state, instance})
180+
const subscription = refreshStampedToken({state, instance, key: null})
181181
subscriptions.push(subscription)
182182

183183
await vi.advanceTimersToNextTimerAsync()
@@ -202,7 +202,7 @@ describe('refreshStampedToken', () => {
202202
dataset: 'd',
203203
auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
204204
})
205-
const initialState = authStore.getInitialState(instance)
205+
const initialState = authStore.getInitialState(instance, null)
206206
initialState.authState = {
207207
type: AuthStateType.LOGGED_IN,
208208
token: 'sk-initial-token-st123',
@@ -213,7 +213,7 @@ describe('refreshStampedToken', () => {
213213
let subscription: Subscription | undefined
214214
// We expect this NOT to throw, but accept we can't easily test the lock call or outcome
215215
expect(() => {
216-
subscription = refreshStampedToken({state, instance})
216+
subscription = refreshStampedToken({state, instance, key: null})
217217
subscriptions.push(subscription!)
218218
}).not.toThrow()
219219

@@ -253,15 +253,15 @@ describe('refreshStampedToken', () => {
253253
dataset: 'd',
254254
auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
255255
})
256-
const initialState = authStore.getInitialState(instance)
256+
const initialState = authStore.getInitialState(instance, null)
257257
initialState.authState = {
258258
type: AuthStateType.LOGGED_IN,
259259
token: 'sk-initial-token-st123',
260260
currentUser: null,
261261
}
262262
const state = createStoreState(initialState)
263263

264-
const subscription = refreshStampedToken({state, instance})
264+
const subscription = refreshStampedToken({state, instance, key: null})
265265
subscriptions.push(subscription)
266266

267267
// DO NOT advance timers or yield here - focus on immediate observable logic
@@ -303,15 +303,15 @@ describe('refreshStampedToken', () => {
303303
dataset: 'd',
304304
auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
305305
})
306-
const initialState = authStore.getInitialState(instance)
306+
const initialState = authStore.getInitialState(instance, null)
307307
initialState.authState = {
308308
type: AuthStateType.LOGGED_IN,
309309
token: 'sk-initial-token-st123',
310310
currentUser: null,
311311
}
312312
const state = createStoreState(initialState)
313313

314-
const subscription = refreshStampedToken({state, instance})
314+
const subscription = refreshStampedToken({state, instance, key: null})
315315
subscriptions.push(subscription)
316316

317317
// Advance timers to allow the async `performRefresh` to execute
@@ -349,7 +349,7 @@ describe('refreshStampedToken', () => {
349349
dataset: 'd',
350350
auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
351351
})
352-
const initialState = authStore.getInitialState(instance)
352+
const initialState = authStore.getInitialState(instance, null)
353353
initialState.authState = {
354354
type: AuthStateType.LOGGED_IN,
355355
token: 'sk-initial-token-st123',
@@ -358,7 +358,7 @@ describe('refreshStampedToken', () => {
358358
initialState.dashboardContext = {mode: 'test'}
359359
const state = createStoreState(initialState)
360360

361-
const subscription = refreshStampedToken({state, instance})
361+
const subscription = refreshStampedToken({state, instance, key: null})
362362
subscriptions.push(subscription)
363363

364364
await vi.advanceTimersToNextTimerAsync()
@@ -378,14 +378,14 @@ describe('refreshStampedToken', () => {
378378
dataset: 'd',
379379
auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
380380
})
381-
const initialState = authStore.getInitialState(instance)
381+
const initialState = authStore.getInitialState(instance, null)
382382
initialState.authState = {
383383
type: AuthStateType.LOGGED_OUT,
384384
isDestroyingSession: false,
385385
} as AuthState
386386
const state = createStoreState(initialState)
387387

388-
const subscription = refreshStampedToken({state, instance})
388+
const subscription = refreshStampedToken({state, instance, key: null})
389389
subscriptions.push(subscription)
390390

391391
await vi.advanceTimersByTimeAsync(0)
@@ -404,15 +404,15 @@ describe('refreshStampedToken', () => {
404404
dataset: 'd',
405405
auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
406406
})
407-
const initialState = authStore.getInitialState(instance)
407+
const initialState = authStore.getInitialState(instance, null)
408408
initialState.authState = {
409409
type: AuthStateType.LOGGED_IN,
410410
token: 'sk-nonstamped-token',
411411
currentUser: null,
412412
}
413413
const state = createStoreState(initialState)
414414

415-
const subscription = refreshStampedToken({state, instance})
415+
const subscription = refreshStampedToken({state, instance, key: null})
416416
subscriptions.push(subscription)
417417

418418
await vi.advanceTimersByTimeAsync(0)

packages/core/src/auth/subscribeToStateAndFetchCurrentUser.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('subscribeToStateAndFetchCurrentUser', () => {
2020
const clientFactory = vi.fn().mockReturnValue(mockClient)
2121
const instance = createSanityInstance({projectId: 'p', dataset: 'd', auth: {clientFactory}})
2222

23-
const state = createStoreState(authStore.getInitialState(instance))
23+
const state = createStoreState(authStore.getInitialState(instance, null))
2424
const subscription = subscribeToStateAndFetchCurrentUser({state, instance})
2525

2626
expect(state.get()).toMatchObject({authState: {type: AuthStateType.LOGGED_OUT}})
@@ -52,7 +52,7 @@ describe('subscribeToStateAndFetchCurrentUser', () => {
5252
const clientFactory = vi.fn().mockReturnValue(mockClient)
5353
const instance = createSanityInstance({projectId: 'p', dataset: 'd', auth: {clientFactory}})
5454

55-
const state = createStoreState(authStore.getInitialState(instance))
55+
const state = createStoreState(authStore.getInitialState(instance, null))
5656
const subscription = subscribeToStateAndFetchCurrentUser({state, instance})
5757

5858
expect(state.get()).toMatchObject({authState: {type: AuthStateType.LOGGED_OUT}})
@@ -88,7 +88,7 @@ describe('subscribeToStateAndFetchCurrentUser', () => {
8888
const clientFactory = vi.fn().mockReturnValue(mockClient)
8989
const instance = createSanityInstance({projectId: 'p', dataset: 'd', auth: {clientFactory}})
9090

91-
const state = createStoreState(authStore.getInitialState(instance))
91+
const state = createStoreState(authStore.getInitialState(instance, null))
9292
const subscription = subscribeToStateAndFetchCurrentUser({state, instance})
9393

9494
expect(state.get()).toMatchObject({authState: {type: AuthStateType.LOGGED_OUT}})

packages/core/src/auth/subscribeToStorageEventsAndSetToken.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('subscribeToStorageEventsAndSetToken', () => {
3434
})
3535

3636
it('sets the state to logged in when a matching storage event returns a token', () => {
37-
const state = createStoreState(authStore.getInitialState(instance))
37+
const state = createStoreState(authStore.getInitialState(instance, null))
3838
const {storageKey} = state.get().options
3939
const subscription = subscribeToStorageEventsAndSetToken({state, instance})
4040

@@ -54,7 +54,7 @@ describe('subscribeToStorageEventsAndSetToken', () => {
5454

5555
it('sets the state to logged in when a matching storage event returns null', () => {
5656
vi.mocked(getTokenFromStorage).mockReturnValue('existing-token')
57-
const state = createStoreState(authStore.getInitialState(instance))
57+
const state = createStoreState(authStore.getInitialState(instance, null))
5858
const {storageKey} = state.get().options
5959

6060
const subscription = subscribeToStorageEventsAndSetToken({state, instance})

packages/core/src/comlink/controller/actions/getOrCreateController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const getOrCreateController = (
2121
// if the target origin has changed, we'll create a new controller,
2222
// but need to clean up first
2323
if (controller) {
24-
destroyController({state, instance})
24+
destroyController({state, instance, key: undefined})
2525
}
2626

2727
const newController = createController({targetOrigin})

packages/core/src/document/documentStore.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ import {
2828

2929
import {getClientState} from '../client/clientStore'
3030
import {type DocumentHandle} from '../config/sanityConfig'
31-
import {bindActionByDataset, type StoreAction} from '../store/createActionBinder'
31+
import {
32+
bindActionByDataset,
33+
type BoundDatasetKey,
34+
type StoreAction,
35+
} from '../store/createActionBinder'
3236
import {type SanityInstance} from '../store/createSanityInstance'
3337
import {createStateSourceAction, type StateSource} from '../store/createStateSourceAction'
3438
import {defineStore, type StoreContext} from '../store/defineStore'
@@ -103,7 +107,7 @@ export interface DocumentState {
103107
unverifiedRevisions?: {[TTransactionId in string]?: UnverifiedDocumentRevision}
104108
}
105109

106-
export const documentStore = defineStore<DocumentStoreState>({
110+
export const documentStore = defineStore<DocumentStoreState, BoundDatasetKey>({
107111
name: 'Document',
108112
getInitialState: (instance) => ({
109113
documentStates: {},
@@ -443,8 +447,8 @@ const subscribeToSubscriptionsAndListenToDocuments = (
443447
const subscribeToClientAndFetchDatasetAcl = ({
444448
instance,
445449
state,
446-
}: StoreContext<DocumentStoreState>) => {
447-
const {projectId, dataset} = instance.config
450+
key: {projectId, dataset},
451+
}: StoreContext<DocumentStoreState, BoundDatasetKey>) => {
448452
return getClientState(instance, {apiVersion: API_VERSION})
449453
.observable.pipe(
450454
switchMap((client) =>

packages/core/src/presence/presenceStore.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {combineLatest, distinctUntilChanged, filter, map, of, Subscription, swit
44

55
import {getTokenState} from '../auth/authStore'
66
import {getClient} from '../client/clientStore'
7-
import {bindActionByDataset} from '../store/createActionBinder'
7+
import {bindActionByDataset, type BoundDatasetKey} from '../store/createActionBinder'
88
import {createStateSourceAction, type SelectorContext} from '../store/createStateSourceAction'
99
import {defineStore, type StoreContext} from '../store/defineStore'
1010
import {type SanityUser} from '../users/types'
@@ -23,15 +23,21 @@ const getInitialState = (): PresenceStoreState => ({
2323
})
2424

2525
/** @public */
26-
export const presenceStore = defineStore<PresenceStoreState>({
26+
export const presenceStore = defineStore<PresenceStoreState, BoundDatasetKey>({
2727
name: 'presence',
2828
getInitialState,
29-
initialize: (context: StoreContext<PresenceStoreState>) => {
30-
const {instance, state} = context
29+
initialize: (context: StoreContext<PresenceStoreState, BoundDatasetKey>) => {
30+
const {
31+
instance,
32+
state,
33+
key: {projectId, dataset},
34+
} = context
3135
const sessionId = crypto.randomUUID()
3236

3337
const client = getClient(instance, {
3438
apiVersion: '2022-06-30',
39+
projectId,
40+
dataset,
3541
})
3642

3743
const token$ = getTokenState(instance).observable.pipe(distinctUntilChanged())
@@ -114,7 +120,7 @@ export const getPresence = bindActionByDataset(
114120
createStateSourceAction({
115121
selector: (context: SelectorContext<PresenceStoreState>): UserPresence[] =>
116122
selectPresence(context.state),
117-
onSubscribe: (context) => {
123+
onSubscribe: (context: StoreContext<PresenceStoreState, BoundDatasetKey>) => {
118124
const userIds$ = context.state.observable.pipe(
119125
map((state) =>
120126
Array.from(state.locations.values())
@@ -134,7 +140,7 @@ export const getPresence = bindActionByDataset(
134140
getUserState(context.instance, {
135141
userId,
136142
resourceType: 'project',
137-
projectId: context.instance.config.projectId,
143+
projectId: context.key.projectId,
138144
}).pipe(filter((v): v is NonNullable<typeof v> => !!v)),
139145
)
140146
return combineLatest(userObservables)

0 commit comments

Comments
 (0)