diff --git a/packages/core/src/auth/authStore.test.ts b/packages/core/src/auth/authStore.test.ts index 65b3be83d..88422f85d 100644 --- a/packages/core/src/auth/authStore.test.ts +++ b/packages/core/src/auth/authStore.test.ts @@ -92,7 +92,7 @@ describe('authStore', () => { }, }) - const {options, dashboardContext} = authStore.getInitialState(instance) + const {options, dashboardContext} = authStore.getInitialState(instance, null) expect(options.apiHost).toBe(apiHost) expect(options.callbackUrl).toBe(callbackUrl) @@ -114,7 +114,7 @@ describe('authStore', () => { auth: {initialLocationHref}, }) - const {dashboardContext, authState} = authStore.getInitialState(instance) + const {dashboardContext, authState} = authStore.getInitialState(instance, null) expect(dashboardContext).toEqual(context) expect(authState.type).toBe(AuthStateType.LOGGED_OUT) }) @@ -129,7 +129,7 @@ describe('authStore', () => { auth: {initialLocationHref}, }) - const {dashboardContext, authState} = authStore.getInitialState(instance) + const {dashboardContext, authState} = authStore.getInitialState(instance, null) expect(dashboardContext).toEqual(expectedContext) expect(authState.type).toBe(AuthStateType.LOGGED_OUT) }) @@ -143,7 +143,7 @@ describe('authStore', () => { auth: {initialLocationHref}, }) - const {dashboardContext, authState} = authStore.getInitialState(instance) + const {dashboardContext, authState} = authStore.getInitialState(instance, null) expect(dashboardContext).toStrictEqual({}) expect(authState.type).toBe(AuthStateType.LOGGED_OUT) expect(errorSpy).toHaveBeenCalledWith( @@ -166,7 +166,7 @@ describe('authStore', () => { }, }) - const {authState, dashboardContext} = authStore.getInitialState(instance) + const {authState, dashboardContext} = authStore.getInitialState(instance, null) expect(authState).toMatchObject({type: AuthStateType.LOGGED_IN, token}) expect(dashboardContext).toEqual(context) }) @@ -182,7 +182,7 @@ describe('authStore', () => { vi.mocked(getAuthCode).mockReturnValue('auth-code') - const {authState, dashboardContext} = authStore.getInitialState(instance) + const {authState, dashboardContext} = authStore.getInitialState(instance, null) expect(authState).toMatchObject({type: AuthStateType.LOGGING_IN}) expect(dashboardContext).toEqual(context) }) @@ -197,7 +197,7 @@ describe('authStore', () => { vi.mocked(getAuthCode).mockReturnValue(null) vi.mocked(getTokenFromStorage).mockReturnValue(storageToken) - const {authState, dashboardContext} = authStore.getInitialState(instance) + const {authState, dashboardContext} = authStore.getInitialState(instance, null) expect(authState).toMatchObject({type: AuthStateType.LOGGED_IN, token: storageToken}) expect(dashboardContext).toStrictEqual({}) }) @@ -215,7 +215,7 @@ describe('authStore', () => { vi.mocked(getAuthCode).mockReturnValue(null) vi.mocked(getTokenFromStorage).mockReturnValue(storageToken) - const {authState, dashboardContext} = authStore.getInitialState(instance) + const {authState, dashboardContext} = authStore.getInitialState(instance, null) expect(authState).toMatchObject({type: AuthStateType.LOGGED_OUT}) expect(dashboardContext).toEqual(context) }) @@ -229,7 +229,7 @@ describe('authStore', () => { vi.mocked(getAuthCode).mockReturnValue(null) vi.mocked(getTokenFromStorage).mockReturnValue(null) - const {authState, dashboardContext} = authStore.getInitialState(instance) + const {authState, dashboardContext} = authStore.getInitialState(instance, null) expect(authState).toMatchObject({type: AuthStateType.LOGGED_OUT}) expect(dashboardContext).toStrictEqual({}) }) @@ -251,7 +251,7 @@ describe('authStore', () => { auth: {storageArea: mockStorage}, // Provide mock storage }) - const {authState, options} = authStore.getInitialState(instance) + const {authState, options} = authStore.getInitialState(instance, null) expect(getStudioTokenFromLocalStorage).toHaveBeenCalledWith(mockStorage, projectId) expect(authState).toMatchObject({type: AuthStateType.LOGGED_IN, token: studioToken}) expect(options.authMethod).toBe('localstorage') @@ -277,7 +277,7 @@ describe('authStore', () => { }) // Initial state might be logged out before the async check completes - const {authState: initialAuthState} = authStore.getInitialState(instance) + const {authState: initialAuthState} = authStore.getInitialState(instance, null) expect(initialAuthState.type).toBe(AuthStateType.LOGGED_OUT) // Or potentially logging in depending on other factors expect(getStudioTokenFromLocalStorage).toHaveBeenCalledWith(mockStorage, projectId) expect(checkForCookieAuth).toHaveBeenCalledWith(projectId, expect.any(Function)) @@ -297,7 +297,7 @@ describe('authStore', () => { dataset: 'd', }) - const {authState, options} = authStore.getInitialState(instance) + const {authState, options} = authStore.getInitialState(instance, null) expect(getStudioTokenFromLocalStorage).not.toHaveBeenCalled() expect(checkForCookieAuth).not.toHaveBeenCalled() expect(getTokenFromStorage).toHaveBeenCalled() @@ -315,7 +315,7 @@ describe('authStore', () => { vi.mocked(getAuthCode).mockReturnValue(null) vi.mocked(getTokenFromLocation).mockReturnValue('hash-token') - const {authState} = authStore.getInitialState(instance) + const {authState} = authStore.getInitialState(instance, null) expect(authState).toMatchObject({ type: AuthStateType.LOGGING_IN, isExchangingToken: false, diff --git a/packages/core/src/auth/refreshStampedToken.test.ts b/packages/core/src/auth/refreshStampedToken.test.ts index 83fe3eb4c..b2f0ad3c8 100644 --- a/packages/core/src/auth/refreshStampedToken.test.ts +++ b/packages/core/src/auth/refreshStampedToken.test.ts @@ -122,7 +122,7 @@ describe('refreshStampedToken', () => { dataset: 'd', auth: {clientFactory: mockClientFactory, storageArea: mockStorage}, }) - const initialState = authStore.getInitialState(instance) + const initialState = authStore.getInitialState(instance, null) initialState.authState = { type: AuthStateType.LOGGED_IN, token: 'sk-initial-token-st123', @@ -131,7 +131,7 @@ describe('refreshStampedToken', () => { initialState.dashboardContext = {mode: 'test'} const state = createStoreState(initialState) - const subscription = refreshStampedToken({state, instance}) + const subscription = refreshStampedToken({state, instance, key: null}) subscriptions.push(subscription) await vi.advanceTimersToNextTimerAsync() @@ -168,7 +168,7 @@ describe('refreshStampedToken', () => { dataset: 'd', auth: {clientFactory: mockClientFactory, storageArea: mockStorage}, }) - const initialState = authStore.getInitialState(instance) + const initialState = authStore.getInitialState(instance, null) initialState.authState = { type: AuthStateType.LOGGED_IN, token: 'sk-initial-token-st123', @@ -177,7 +177,7 @@ describe('refreshStampedToken', () => { initialState.dashboardContext = {mode: 'test'} const state = createStoreState(initialState) - const subscription = refreshStampedToken({state, instance}) + const subscription = refreshStampedToken({state, instance, key: null}) subscriptions.push(subscription) await vi.advanceTimersToNextTimerAsync() @@ -202,7 +202,7 @@ describe('refreshStampedToken', () => { dataset: 'd', auth: {clientFactory: mockClientFactory, storageArea: mockStorage}, }) - const initialState = authStore.getInitialState(instance) + const initialState = authStore.getInitialState(instance, null) initialState.authState = { type: AuthStateType.LOGGED_IN, token: 'sk-initial-token-st123', @@ -213,7 +213,7 @@ describe('refreshStampedToken', () => { let subscription: Subscription | undefined // We expect this NOT to throw, but accept we can't easily test the lock call or outcome expect(() => { - subscription = refreshStampedToken({state, instance}) + subscription = refreshStampedToken({state, instance, key: null}) subscriptions.push(subscription!) }).not.toThrow() @@ -253,7 +253,7 @@ describe('refreshStampedToken', () => { dataset: 'd', auth: {clientFactory: mockClientFactory, storageArea: mockStorage}, }) - const initialState = authStore.getInitialState(instance) + const initialState = authStore.getInitialState(instance, null) initialState.authState = { type: AuthStateType.LOGGED_IN, token: 'sk-initial-token-st123', @@ -261,7 +261,7 @@ describe('refreshStampedToken', () => { } const state = createStoreState(initialState) - const subscription = refreshStampedToken({state, instance}) + const subscription = refreshStampedToken({state, instance, key: null}) subscriptions.push(subscription) // DO NOT advance timers or yield here - focus on immediate observable logic @@ -303,7 +303,7 @@ describe('refreshStampedToken', () => { dataset: 'd', auth: {clientFactory: mockClientFactory, storageArea: mockStorage}, }) - const initialState = authStore.getInitialState(instance) + const initialState = authStore.getInitialState(instance, null) initialState.authState = { type: AuthStateType.LOGGED_IN, token: 'sk-initial-token-st123', @@ -311,7 +311,7 @@ describe('refreshStampedToken', () => { } const state = createStoreState(initialState) - const subscription = refreshStampedToken({state, instance}) + const subscription = refreshStampedToken({state, instance, key: null}) subscriptions.push(subscription) // Advance timers to allow the async `performRefresh` to execute @@ -349,7 +349,7 @@ describe('refreshStampedToken', () => { dataset: 'd', auth: {clientFactory: mockClientFactory, storageArea: mockStorage}, }) - const initialState = authStore.getInitialState(instance) + const initialState = authStore.getInitialState(instance, null) initialState.authState = { type: AuthStateType.LOGGED_IN, token: 'sk-initial-token-st123', @@ -358,7 +358,7 @@ describe('refreshStampedToken', () => { initialState.dashboardContext = {mode: 'test'} const state = createStoreState(initialState) - const subscription = refreshStampedToken({state, instance}) + const subscription = refreshStampedToken({state, instance, key: null}) subscriptions.push(subscription) await vi.advanceTimersToNextTimerAsync() @@ -378,14 +378,14 @@ describe('refreshStampedToken', () => { dataset: 'd', auth: {clientFactory: mockClientFactory, storageArea: mockStorage}, }) - const initialState = authStore.getInitialState(instance) + const initialState = authStore.getInitialState(instance, null) initialState.authState = { type: AuthStateType.LOGGED_OUT, isDestroyingSession: false, } as AuthState const state = createStoreState(initialState) - const subscription = refreshStampedToken({state, instance}) + const subscription = refreshStampedToken({state, instance, key: null}) subscriptions.push(subscription) await vi.advanceTimersByTimeAsync(0) @@ -404,7 +404,7 @@ describe('refreshStampedToken', () => { dataset: 'd', auth: {clientFactory: mockClientFactory, storageArea: mockStorage}, }) - const initialState = authStore.getInitialState(instance) + const initialState = authStore.getInitialState(instance, null) initialState.authState = { type: AuthStateType.LOGGED_IN, token: 'sk-nonstamped-token', @@ -412,7 +412,7 @@ describe('refreshStampedToken', () => { } const state = createStoreState(initialState) - const subscription = refreshStampedToken({state, instance}) + const subscription = refreshStampedToken({state, instance, key: null}) subscriptions.push(subscription) await vi.advanceTimersByTimeAsync(0) diff --git a/packages/core/src/auth/subscribeToStateAndFetchCurrentUser.test.ts b/packages/core/src/auth/subscribeToStateAndFetchCurrentUser.test.ts index ff91dd7f8..2316d194c 100644 --- a/packages/core/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +++ b/packages/core/src/auth/subscribeToStateAndFetchCurrentUser.test.ts @@ -20,8 +20,8 @@ describe('subscribeToStateAndFetchCurrentUser', () => { const clientFactory = vi.fn().mockReturnValue(mockClient) const instance = createSanityInstance({projectId: 'p', dataset: 'd', auth: {clientFactory}}) - const state = createStoreState(authStore.getInitialState(instance)) - const subscription = subscribeToStateAndFetchCurrentUser({state, instance}) + const state = createStoreState(authStore.getInitialState(instance, null)) + const subscription = subscribeToStateAndFetchCurrentUser({state, instance, key: null}) expect(state.get()).toMatchObject({authState: {type: AuthStateType.LOGGED_OUT}}) @@ -52,8 +52,8 @@ describe('subscribeToStateAndFetchCurrentUser', () => { const clientFactory = vi.fn().mockReturnValue(mockClient) const instance = createSanityInstance({projectId: 'p', dataset: 'd', auth: {clientFactory}}) - const state = createStoreState(authStore.getInitialState(instance)) - const subscription = subscribeToStateAndFetchCurrentUser({state, instance}) + const state = createStoreState(authStore.getInitialState(instance, null)) + const subscription = subscribeToStateAndFetchCurrentUser({state, instance, key: null}) expect(state.get()).toMatchObject({authState: {type: AuthStateType.LOGGED_OUT}}) @@ -88,8 +88,8 @@ describe('subscribeToStateAndFetchCurrentUser', () => { const clientFactory = vi.fn().mockReturnValue(mockClient) const instance = createSanityInstance({projectId: 'p', dataset: 'd', auth: {clientFactory}}) - const state = createStoreState(authStore.getInitialState(instance)) - const subscription = subscribeToStateAndFetchCurrentUser({state, instance}) + const state = createStoreState(authStore.getInitialState(instance, null)) + const subscription = subscribeToStateAndFetchCurrentUser({state, instance, key: null}) expect(state.get()).toMatchObject({authState: {type: AuthStateType.LOGGED_OUT}}) diff --git a/packages/core/src/auth/subscribeToStorageEventsAndSetToken.test.ts b/packages/core/src/auth/subscribeToStorageEventsAndSetToken.test.ts index c72ff0672..5f7187a99 100644 --- a/packages/core/src/auth/subscribeToStorageEventsAndSetToken.test.ts +++ b/packages/core/src/auth/subscribeToStorageEventsAndSetToken.test.ts @@ -34,9 +34,9 @@ describe('subscribeToStorageEventsAndSetToken', () => { }) it('sets the state to logged in when a matching storage event returns a token', () => { - const state = createStoreState(authStore.getInitialState(instance)) + const state = createStoreState(authStore.getInitialState(instance, null)) const {storageKey} = state.get().options - const subscription = subscribeToStorageEventsAndSetToken({state, instance}) + const subscription = subscribeToStorageEventsAndSetToken({state, instance, key: null}) expect(state.get()).toMatchObject({ authState: {type: AuthStateType.LOGGED_OUT, isDestroyingSession: false}, @@ -54,10 +54,10 @@ describe('subscribeToStorageEventsAndSetToken', () => { it('sets the state to logged in when a matching storage event returns null', () => { vi.mocked(getTokenFromStorage).mockReturnValue('existing-token') - const state = createStoreState(authStore.getInitialState(instance)) + const state = createStoreState(authStore.getInitialState(instance, null)) const {storageKey} = state.get().options - const subscription = subscribeToStorageEventsAndSetToken({state, instance}) + const subscription = subscribeToStorageEventsAndSetToken({state, instance, key: null}) expect(state.get()).toMatchObject({ authState: {type: AuthStateType.LOGGED_IN, token: 'existing-token', currentUser: null}, diff --git a/packages/core/src/comlink/controller/actions/destroyController.test.ts b/packages/core/src/comlink/controller/actions/destroyController.test.ts index d253daeb4..edefe419c 100644 --- a/packages/core/src/comlink/controller/actions/destroyController.test.ts +++ b/packages/core/src/comlink/controller/actions/destroyController.test.ts @@ -39,7 +39,7 @@ describe('destroyController', () => { }) // Execute action - destroyController({state, instance}) + destroyController({state, instance, key: null}) // Verify controller was destroyed and state was cleared expect(mockController.destroy).toHaveBeenCalled() @@ -49,7 +49,7 @@ describe('destroyController', () => { it('should do nothing if no controller exists', () => { // State already has null controller, so just execute action - expect(() => destroyController({state, instance})).not.toThrow() + expect(() => destroyController({state, instance, key: null})).not.toThrow() // State should remain unchanged expect(state.get().controller).toBeNull() diff --git a/packages/core/src/comlink/controller/actions/getOrCreateChannel.test.ts b/packages/core/src/comlink/controller/actions/getOrCreateChannel.test.ts index 3a13de4d6..b0215b6dd 100644 --- a/packages/core/src/comlink/controller/actions/getOrCreateChannel.test.ts +++ b/packages/core/src/comlink/controller/actions/getOrCreateChannel.test.ts @@ -49,7 +49,7 @@ describe('getOrCreateChannel', () => { it('should create a new channel using the controller', () => { const createChannelSpy = vi.spyOn(mockController, 'createChannel') - const channel = getOrCreateChannel({state, instance}, channelConfig) + const channel = getOrCreateChannel({state, instance, key: null}, channelConfig) expect(createChannelSpy).toHaveBeenCalledWith(channelConfig) expect(channel.on).toBeDefined() @@ -70,17 +70,17 @@ describe('getOrCreateChannel', () => { channels: new Map(), }) - expect(() => getOrCreateChannel({state, instance}, channelConfig)).toThrow( + expect(() => getOrCreateChannel({state, instance, key: null}, channelConfig)).toThrow( 'Controller must be initialized before using or creating channels', ) }) it('should retrieve channel directly from store once created', () => { - const createdChannel = getOrCreateChannel({state, instance}, channelConfig) + const createdChannel = getOrCreateChannel({state, instance, key: null}, channelConfig) vi.clearAllMocks() // Clear call counts // Retrieve channel again - const retrievedChannel = getOrCreateChannel({state, instance}, channelConfig) + const retrievedChannel = getOrCreateChannel({state, instance, key: null}, channelConfig) expect(retrievedChannel).toBeDefined() expect(retrievedChannel).toBe(createdChannel) @@ -95,10 +95,10 @@ describe('getOrCreateChannel', () => { }) it('should throw error when trying to create channel with different options', () => { - getOrCreateChannel({state, instance}, channelConfig) + getOrCreateChannel({state, instance, key: null}, channelConfig) expect(() => - getOrCreateChannel({state, instance}, {...channelConfig, connectTo: 'window'}), + getOrCreateChannel({state, instance, key: null}, {...channelConfig, connectTo: 'window'}), ).toThrow('Channel "test" already exists with different options') }) }) diff --git a/packages/core/src/comlink/controller/actions/getOrCreateController.test.ts b/packages/core/src/comlink/controller/actions/getOrCreateController.test.ts index 181e5d3d4..0fb50b2b6 100644 --- a/packages/core/src/comlink/controller/actions/getOrCreateController.test.ts +++ b/packages/core/src/comlink/controller/actions/getOrCreateController.test.ts @@ -43,7 +43,7 @@ describe('getOrCreateController', () => { const controllerSpy = vi.spyOn(comlink, 'createController') const targetOrigin = 'https://test.sanity.dev' - const controller = getOrCreateController({state, instance}, targetOrigin) + const controller = getOrCreateController({state, instance, key: null}, targetOrigin) expect(controllerSpy).toHaveBeenCalledWith({targetOrigin}) expect(controller).toBeDefined() @@ -56,8 +56,8 @@ describe('getOrCreateController', () => { const controllerSpy = vi.spyOn(comlink, 'createController') const targetOrigin = 'https://test.sanity.dev' - const firstController = getOrCreateController({state, instance}, targetOrigin) - const secondController = getOrCreateController({state, instance}, targetOrigin) + const firstController = getOrCreateController({state, instance, key: null}, targetOrigin) + const secondController = getOrCreateController({state, instance, key: null}, targetOrigin) expect(controllerSpy).toHaveBeenCalledTimes(1) expect(firstController).toBe(secondController) @@ -68,9 +68,9 @@ describe('getOrCreateController', () => { const targetOrigin = 'https://test.sanity.dev' const targetOrigin2 = 'https://test2.sanity.dev' - const firstController = getOrCreateController({state, instance}, targetOrigin) + const firstController = getOrCreateController({state, instance, key: null}, targetOrigin) const destroySpy = vi.spyOn(firstController, 'destroy') - const secondController = getOrCreateController({state, instance}, targetOrigin2) + const secondController = getOrCreateController({state, instance, key: null}, targetOrigin2) expect(controllerSpy).toHaveBeenCalledTimes(2) expect(destroySpy).toHaveBeenCalled() diff --git a/packages/core/src/comlink/controller/actions/getOrCreateController.ts b/packages/core/src/comlink/controller/actions/getOrCreateController.ts index 7b77c0268..1cd1bd101 100644 --- a/packages/core/src/comlink/controller/actions/getOrCreateController.ts +++ b/packages/core/src/comlink/controller/actions/getOrCreateController.ts @@ -21,7 +21,7 @@ export const getOrCreateController = ( // if the target origin has changed, we'll create a new controller, // but need to clean up first if (controller) { - destroyController({state, instance}) + destroyController({state, instance, key: undefined}) } const newController = createController({targetOrigin}) diff --git a/packages/core/src/comlink/controller/actions/releaseChannel.test.ts b/packages/core/src/comlink/controller/actions/releaseChannel.test.ts index 5738e9d10..39947c6c4 100644 --- a/packages/core/src/comlink/controller/actions/releaseChannel.test.ts +++ b/packages/core/src/comlink/controller/actions/releaseChannel.test.ts @@ -19,6 +19,7 @@ const channelConfig = { describe('releaseChannel', () => { let instance: SanityInstance let store: StoreInstance + const key = {name: 'global', projectId: 'test-project-id', dataset: 'test-dataset'} let getOrCreateChannel: ( inst: SanityInstance, @@ -29,14 +30,14 @@ describe('releaseChannel', () => { beforeEach(() => { instance = createSanityInstance({projectId: 'test-project-id', dataset: 'test-dataset'}) - store = createStoreInstance(instance, comlinkControllerStore) + store = createStoreInstance(instance, key, comlinkControllerStore) const bind = ( action: StoreAction, ) => (inst: SanityInstance, ...params: TParams) => - action({instance: inst, state: store.state}, ...params) + action({instance: inst, state: store.state, key}, ...params) getOrCreateChannel = bind(unboundGetOrCreateChannel) getOrCreateController = bind(unboundGetOrCreateController) diff --git a/packages/core/src/comlink/controller/comlinkControllerStore.test.ts b/packages/core/src/comlink/controller/comlinkControllerStore.test.ts index d2edfc819..ec76719f8 100644 --- a/packages/core/src/comlink/controller/comlinkControllerStore.test.ts +++ b/packages/core/src/comlink/controller/comlinkControllerStore.test.ts @@ -31,7 +31,7 @@ describe('comlinkControllerStore', () => { // Create store state directly const state = createStoreState( - comlinkControllerStore.getInitialState(instance), + comlinkControllerStore.getInitialState(instance, null), ) const initialState = state.get() @@ -56,13 +56,13 @@ describe('comlinkControllerStore', () => { vi.mocked(bindActionGlobally).mockImplementation( (_storeDef, action) => (inst: SanityInstance, ...params: unknown[]) => - action({instance: inst, state}, ...params), + action({instance: inst, state, key: {name: 'global'}}, ...params), ) const {comlinkControllerStore} = await import('./comlinkControllerStore') // Get the cleanup function from the store - const dispose = comlinkControllerStore.initialize?.({state, instance}) + const dispose = comlinkControllerStore.initialize?.({state, instance, key: null}) // Run cleanup dispose?.() @@ -82,7 +82,7 @@ describe('comlinkControllerStore', () => { }) // Get the cleanup function - const cleanup = comlinkControllerStore.initialize?.({state, instance}) + const cleanup = comlinkControllerStore.initialize?.({state, instance, key: null}) // Should not throw when no controller exists expect(() => cleanup?.()).not.toThrow() diff --git a/packages/core/src/comlink/node/actions/getOrCreateNode.test.ts b/packages/core/src/comlink/node/actions/getOrCreateNode.test.ts index 3b6daad18..bca6488b7 100644 --- a/packages/core/src/comlink/node/actions/getOrCreateNode.test.ts +++ b/packages/core/src/comlink/node/actions/getOrCreateNode.test.ts @@ -40,24 +40,24 @@ describe('getOrCreateNode', () => { }) it('should create and start a node', () => { - const node = getOrCreateNode({state, instance}, nodeConfig) + const node = getOrCreateNode({state, instance, key: null}, nodeConfig) expect(comlink.createNode).toHaveBeenCalledWith(nodeConfig) expect(node.start).toHaveBeenCalled() }) it('should store the node in nodeStore', () => { - const node = getOrCreateNode({state, instance}, nodeConfig) + const node = getOrCreateNode({state, instance, key: null}, nodeConfig) - expect(getOrCreateNode({state, instance}, nodeConfig)).toBe(node) + expect(getOrCreateNode({state, instance, key: null}, nodeConfig)).toBe(node) }) it('should throw error when trying to create node with different options', () => { - getOrCreateNode({state, instance}, nodeConfig) + getOrCreateNode({state, instance, key: null}, nodeConfig) expect(() => getOrCreateNode( - {state, instance}, + {state, instance, key: null}, { ...nodeConfig, connectTo: 'window', @@ -74,7 +74,7 @@ describe('getOrCreateNode', () => { return statusUnsubMock }) - getOrCreateNode({state, instance}, nodeConfig) + getOrCreateNode({state, instance, key: null}, nodeConfig) expect(mockNode.onStatus).toHaveBeenCalled() expect(state.get().nodes.get(nodeConfig.name)?.statusUnsub).toBe(statusUnsubMock) @@ -92,7 +92,7 @@ describe('getOrCreateNode', () => { return statusUnsubMock }) - getOrCreateNode({state, instance}, nodeConfig) + getOrCreateNode({state, instance, key: null}, nodeConfig) // Remove the node entry before triggering the status callback state.get().nodes.delete(nodeConfig.name) diff --git a/packages/core/src/comlink/node/actions/releaseNode.test.ts b/packages/core/src/comlink/node/actions/releaseNode.test.ts index 1c710dd0b..a8643b804 100644 --- a/packages/core/src/comlink/node/actions/releaseNode.test.ts +++ b/packages/core/src/comlink/node/actions/releaseNode.test.ts @@ -47,7 +47,7 @@ describe('releaseNode', () => { expect(state.get().nodes.has('test-node')).toBe(true) // Release the node - releaseNode({state, instance}, 'test-node') + releaseNode({state, instance, key: null}, 'test-node') // Check node is removed expect(mockNode.stop).toHaveBeenCalled() @@ -64,7 +64,7 @@ describe('releaseNode', () => { }) state.set('setup', {nodes}) - releaseNode({state, instance}, 'test-node') + releaseNode({state, instance, key: null}, 'test-node') expect(statusUnsub).toHaveBeenCalled() }) diff --git a/packages/core/src/comlink/node/comlinkNodeStore.test.ts b/packages/core/src/comlink/node/comlinkNodeStore.test.ts index e6fe3696b..5bfd063e4 100644 --- a/packages/core/src/comlink/node/comlinkNodeStore.test.ts +++ b/packages/core/src/comlink/node/comlinkNodeStore.test.ts @@ -14,7 +14,7 @@ describe('nodeStore', () => { }) it('should have correct initial state', () => { - const initialState = comlinkNodeStore.getInitialState(instance) + const initialState = comlinkNodeStore.getInitialState(instance, null) expect(initialState.nodes).toBeInstanceOf(Map) expect(initialState.nodes.size).toBe(0) @@ -25,7 +25,7 @@ describe('nodeStore', () => { stop: vi.fn(), } as unknown as Node - const initialState = comlinkNodeStore.getInitialState(instance) + const initialState = comlinkNodeStore.getInitialState(instance, null) initialState.nodes.set('test-node', { options: {name: 'test-node', connectTo: 'parent'}, node: mockNode, @@ -35,6 +35,7 @@ describe('nodeStore', () => { const cleanup = comlinkNodeStore.initialize?.({ instance, state: createStoreState(initialState), + key: null, }) cleanup?.() diff --git a/packages/core/src/document/applyDocumentActions.test.ts b/packages/core/src/document/applyDocumentActions.test.ts index f3e1ef78e..9b502b907 100644 --- a/packages/core/src/document/applyDocumentActions.test.ts +++ b/packages/core/src/document/applyDocumentActions.test.ts @@ -48,11 +48,11 @@ describe('applyDocumentActions', () => { } state = createStoreState(initialState) instance = createSanityInstance({projectId: 'p', dataset: 'd'}) + const key = {name: 'p.d', projectId: 'p', dataset: 'd'} vi.mocked(bindActionByDataset).mockImplementation( - (_storeDef, action) => - (instanceParam: SanityInstance, ...params: unknown[]) => - action({instance: instanceParam, state}, ...params), + (_storeDef, action) => (instanceParam: SanityInstance, options) => + action({instance: instanceParam, state, key}, options), ) // Import dynamically to ensure mocks are set up before the module under test is loaded const module = await import('./applyDocumentActions') diff --git a/packages/core/src/document/documentStore.ts b/packages/core/src/document/documentStore.ts index 935fcc53f..3da67d5a0 100644 --- a/packages/core/src/document/documentStore.ts +++ b/packages/core/src/document/documentStore.ts @@ -28,7 +28,11 @@ import { import {getClientState} from '../client/clientStore' import {type DocumentHandle} from '../config/sanityConfig' -import {bindActionByDataset, type StoreAction} from '../store/createActionBinder' +import { + bindActionByDataset, + type BoundDatasetKey, + type StoreAction, +} from '../store/createActionBinder' import {type SanityInstance} from '../store/createSanityInstance' import {createStateSourceAction, type StateSource} from '../store/createStateSourceAction' import {defineStore, type StoreContext} from '../store/defineStore' @@ -103,7 +107,7 @@ export interface DocumentState { unverifiedRevisions?: {[TTransactionId in string]?: UnverifiedDocumentRevision} } -export const documentStore = defineStore({ +export const documentStore = defineStore({ name: 'Document', getInitialState: (instance) => ({ documentStates: {}, @@ -443,8 +447,8 @@ const subscribeToSubscriptionsAndListenToDocuments = ( const subscribeToClientAndFetchDatasetAcl = ({ instance, state, -}: StoreContext) => { - const {projectId, dataset} = instance.config + key: {projectId, dataset}, +}: StoreContext) => { return getClientState(instance, {apiVersion: API_VERSION}) .observable.pipe( switchMap((client) => diff --git a/packages/core/src/presence/presenceStore.test.ts b/packages/core/src/presence/presenceStore.test.ts index 2618c24e0..918bf5682 100644 --- a/packages/core/src/presence/presenceStore.test.ts +++ b/packages/core/src/presence/presenceStore.test.ts @@ -78,7 +78,7 @@ describe('presenceStore', () => { describe('getPresence', () => { it('creates bifur transport with correct parameters', () => { - getPresence(instance) + getPresence(instance, {}) expect(createBifurTransport).toHaveBeenCalledWith({ client: mockClient, @@ -88,18 +88,18 @@ describe('presenceStore', () => { }) it('sends rollCall message on initialization', () => { - getPresence(instance) + getPresence(instance, {}) expect(mockDispatchMessage).toHaveBeenCalledWith({type: 'rollCall'}) }) it('returns empty array when no users present', () => { - const source = getPresence(instance) + const source = getPresence(instance, {}) expect(source.getCurrent()).toEqual([]) }) it('handles state events from other users', async () => { - const source = getPresence(instance) + const source = getPresence(instance, {}) // Subscribe to initialize the store const unsubscribe = source.subscribe(() => {}) @@ -136,7 +136,7 @@ describe('presenceStore', () => { }) it('ignores events from own session', async () => { - const source = getPresence(instance) + const source = getPresence(instance, {}) const unsubscribe = source.subscribe(() => {}) await firstValueFrom(of(null).pipe(delay(10))) @@ -158,7 +158,7 @@ describe('presenceStore', () => { }) it('handles disconnect events', async () => { - const source = getPresence(instance) + const source = getPresence(instance, {}) const unsubscribe = source.subscribe(() => {}) await firstValueFrom(of(null).pipe(delay(10))) @@ -190,7 +190,7 @@ describe('presenceStore', () => { }) it('fetches user data for present users', async () => { - const source = getPresence(instance) + const source = getPresence(instance, {}) const unsubscribe = source.subscribe(() => {}) await firstValueFrom(of(null).pipe(delay(10))) @@ -222,7 +222,7 @@ describe('presenceStore', () => { }) it('handles presence events correctly', async () => { - const source = getPresence(instance) + const source = getPresence(instance, {}) const unsubscribe = source.subscribe(() => {}) await firstValueFrom(of(null).pipe(delay(10))) diff --git a/packages/core/src/presence/presenceStore.ts b/packages/core/src/presence/presenceStore.ts index 02cfc06b9..d2eeea87f 100644 --- a/packages/core/src/presence/presenceStore.ts +++ b/packages/core/src/presence/presenceStore.ts @@ -4,7 +4,7 @@ import {combineLatest, distinctUntilChanged, filter, map, of, Subscription, swit import {getTokenState} from '../auth/authStore' import {getClient} from '../client/clientStore' -import {bindActionByDataset} from '../store/createActionBinder' +import {bindActionByDataset, type BoundDatasetKey} from '../store/createActionBinder' import {createStateSourceAction, type SelectorContext} from '../store/createStateSourceAction' import {defineStore, type StoreContext} from '../store/defineStore' import {type SanityUser} from '../users/types' @@ -23,15 +23,21 @@ const getInitialState = (): PresenceStoreState => ({ }) /** @public */ -export const presenceStore = defineStore({ +export const presenceStore = defineStore({ name: 'presence', getInitialState, - initialize: (context: StoreContext) => { - const {instance, state} = context + initialize: (context: StoreContext) => { + const { + instance, + state, + key: {projectId, dataset}, + } = context const sessionId = crypto.randomUUID() const client = getClient(instance, { apiVersion: '2022-06-30', + projectId, + dataset, }) const token$ = getTokenState(instance).observable.pipe(distinctUntilChanged()) @@ -114,7 +120,7 @@ export const getPresence = bindActionByDataset( createStateSourceAction({ selector: (context: SelectorContext): UserPresence[] => selectPresence(context.state), - onSubscribe: (context) => { + onSubscribe: (context: StoreContext) => { const userIds$ = context.state.observable.pipe( map((state) => Array.from(state.locations.values()) @@ -134,7 +140,7 @@ export const getPresence = bindActionByDataset( getUserState(context.instance, { userId, resourceType: 'project', - projectId: context.instance.config.projectId, + projectId: context.key.projectId, }).pipe(filter((v): v is NonNullable => !!v)), ) return combineLatest(userObservables) diff --git a/packages/core/src/preview/previewStore.test.ts b/packages/core/src/preview/previewStore.test.ts index b13e41292..68ecf5417 100644 --- a/packages/core/src/preview/previewStore.test.ts +++ b/packages/core/src/preview/previewStore.test.ts @@ -18,9 +18,17 @@ describe('previewStore', () => { const instance = createSanityInstance({projectId: 'p', dataset: 'd'}) - const {state, dispose} = createStoreInstance(instance, previewStore) + const {state, dispose} = createStoreInstance( + instance, + {name: 'p.d', projectId: 'p', dataset: 'd'}, + previewStore, + ) - expect(subscribeToStateAndFetchBatches).toHaveBeenCalledWith({instance, state}) + expect(subscribeToStateAndFetchBatches).toHaveBeenCalledWith({ + instance, + state, + key: {name: 'p.d', projectId: 'p', dataset: 'd'}, + }) dispose() instance.dispose() diff --git a/packages/core/src/preview/previewStore.ts b/packages/core/src/preview/previewStore.ts index d32ed9893..c7050cb4e 100644 --- a/packages/core/src/preview/previewStore.ts +++ b/packages/core/src/preview/previewStore.ts @@ -1,3 +1,4 @@ +import {type BoundDatasetKey} from '../store/createActionBinder' import {defineStore} from '../store/defineStore' import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches' @@ -79,7 +80,7 @@ export interface PreviewStoreState { subscriptions: {[TDocumentId in string]?: {[TSubscriptionId in string]?: true}} } -export const previewStore = defineStore({ +export const previewStore = defineStore({ name: 'Preview', getInitialState() { return { diff --git a/packages/core/src/preview/subscribeToStateAndFetchBatches.test.ts b/packages/core/src/preview/subscribeToStateAndFetchBatches.test.ts index 7b07475a7..88e8b3fff 100644 --- a/packages/core/src/preview/subscribeToStateAndFetchBatches.test.ts +++ b/packages/core/src/preview/subscribeToStateAndFetchBatches.test.ts @@ -2,6 +2,7 @@ import {NEVER, Observable, type Observer} from 'rxjs' import {describe, expect, it, vi} from 'vitest' import {getQueryState, resolveQuery} from '../query/queryStore' +import {type BoundDatasetKey} from '../store/createActionBinder' import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance' import {type StateSource} from '../store/createStateSourceAction' import {createStoreState, type StoreState} from '../store/createStoreState' @@ -14,6 +15,7 @@ vi.mock('../query/queryStore') describe('subscribeToStateAndFetchBatches', () => { let instance: SanityInstance let state: StoreState + let key: BoundDatasetKey beforeEach(() => { vi.clearAllMocks() @@ -22,6 +24,7 @@ describe('subscribeToStateAndFetchBatches', () => { subscriptions: {}, values: {}, }) + key = {name: 'test.test', projectId: 'test', dataset: 'test'} vi.mocked(getQueryState).mockReturnValue({ getCurrent: () => undefined, @@ -36,7 +39,7 @@ describe('subscribeToStateAndFetchBatches', () => { }) it('batches rapid subscription changes into single requests', async () => { - const subscription = subscribeToStateAndFetchBatches({instance, state}) + const subscription = subscribeToStateAndFetchBatches({instance, state, key}) // Add multiple subscriptions rapidly state.set('addSubscription1', { @@ -77,7 +80,7 @@ describe('subscribeToStateAndFetchBatches', () => { observable: new Observable(subscriber), } as StateSource) - const subscription = subscribeToStateAndFetchBatches({instance, state}) + const subscription = subscribeToStateAndFetchBatches({instance, state, key}) expect(subscriber).not.toHaveBeenCalled() @@ -126,7 +129,7 @@ describe('subscribeToStateAndFetchBatches', () => { subscriptions: {doc1: {sub1: true}}, }) - const subscription = subscribeToStateAndFetchBatches({instance, state}) + const subscription = subscribeToStateAndFetchBatches({instance, state, key}) // Add a subscription for a document already in the batch state.set('addSubscriptionAlreadyInBatch', (prev) => ({ @@ -152,7 +155,7 @@ describe('subscribeToStateAndFetchBatches', () => { it('cancels and restarts fetches when subscription set changes', async () => { const abortSpy = vi.spyOn(AbortController.prototype, 'abort') - const subscription = subscribeToStateAndFetchBatches({instance, state}) + const subscription = subscribeToStateAndFetchBatches({instance, state, key}) // Add initial subscription state.set('addSubscription1', { @@ -182,7 +185,7 @@ describe('subscribeToStateAndFetchBatches', () => { observable: new Observable(subscriber), } as StateSource) - const subscription = subscribeToStateAndFetchBatches({instance, state}) + const subscription = subscribeToStateAndFetchBatches({instance, state, key}) // Add a subscription state.set('addSubscription', { diff --git a/packages/core/src/preview/subscribeToStateAndFetchBatches.ts b/packages/core/src/preview/subscribeToStateAndFetchBatches.ts index 256565d9a..828fce284 100644 --- a/packages/core/src/preview/subscribeToStateAndFetchBatches.ts +++ b/packages/core/src/preview/subscribeToStateAndFetchBatches.ts @@ -15,6 +15,7 @@ import { } from 'rxjs' import {getQueryState, resolveQuery} from '../query/queryStore' +import {type BoundDatasetKey} from '../store/createActionBinder' import {type StoreContext} from '../store/defineStore' import {createPreviewQuery, processPreviewQuery} from './previewQuery' import {type PreviewQueryResult, type PreviewStoreState} from './previewStore' @@ -28,7 +29,8 @@ const isSetEqual = (a: Set, b: Set) => export const subscribeToStateAndFetchBatches = ({ state, instance, -}: StoreContext): Subscription => { + key: {projectId, dataset}, +}: StoreContext): Subscription => { const newSubscriberIds$ = state.observable.pipe( map(({subscriptions}) => new Set(Object.keys(subscriptions))), distinctUntilChanged(isSetEqual), @@ -64,6 +66,8 @@ export const subscribeToStateAndFetchBatches = ({ params, tag: PREVIEW_TAG, perspective: PREVIEW_PERSPECTIVE, + projectId, + dataset, }) const source$ = defer(() => { if (getCurrent() === undefined) { @@ -74,6 +78,8 @@ export const subscribeToStateAndFetchBatches = ({ tag: PREVIEW_TAG, perspective: PREVIEW_PERSPECTIVE, signal: controller.signal, + projectId, + dataset, }), ).pipe(switchMap(() => observable)) } @@ -91,8 +97,8 @@ export const subscribeToStateAndFetchBatches = ({ }), map(({ids, data}) => ({ values: processPreviewQuery({ - projectId: instance.config.projectId!, - dataset: instance.config.dataset!, + projectId, + dataset, ids, results: data, }), diff --git a/packages/core/src/projection/projectionStore.test.ts b/packages/core/src/projection/projectionStore.test.ts index 3714a46a7..f6028b517 100644 --- a/packages/core/src/projection/projectionStore.test.ts +++ b/packages/core/src/projection/projectionStore.test.ts @@ -26,10 +26,26 @@ describe('projectionStore', () => { const instance = createSanityInstance({projectId: 'p', dataset: 'd'}) - const {state, dispose} = createStoreInstance(instance, projectionStore) + const {state, dispose} = createStoreInstance( + instance, + { + name: 'p.d', + projectId: 'p', + dataset: 'd', + }, + projectionStore, + ) expect(subscribeToStateAndFetchBatches).toHaveBeenCalledOnce() - expect(subscribeToStateAndFetchBatches).toHaveBeenCalledWith({instance, state}) + expect(subscribeToStateAndFetchBatches).toHaveBeenCalledWith({ + instance, + state, + key: { + name: 'p.d', + projectId: 'p', + dataset: 'd', + }, + }) dispose() instance.dispose() diff --git a/packages/core/src/projection/projectionStore.ts b/packages/core/src/projection/projectionStore.ts index a1dec5cc8..a1bdba857 100644 --- a/packages/core/src/projection/projectionStore.ts +++ b/packages/core/src/projection/projectionStore.ts @@ -1,8 +1,9 @@ +import {type BoundDatasetKey} from '../store/createActionBinder' import {defineStore} from '../store/defineStore' import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches' import {type ProjectionStoreState} from './types' -export const projectionStore = defineStore({ +export const projectionStore = defineStore({ name: 'Projection', getInitialState() { return { diff --git a/packages/core/src/projection/subscribeToStateAndFetchBatches.test.ts b/packages/core/src/projection/subscribeToStateAndFetchBatches.test.ts index b416f0bd4..00dfc304a 100644 --- a/packages/core/src/projection/subscribeToStateAndFetchBatches.test.ts +++ b/packages/core/src/projection/subscribeToStateAndFetchBatches.test.ts @@ -15,6 +15,7 @@ vi.mock('../query/queryStore') describe('subscribeToStateAndFetchBatches', () => { let instance: SanityInstance let state: StoreState + const key = {name: 'test.test', projectId: 'test', dataset: 'test'} beforeEach(() => { vi.clearAllMocks() @@ -38,7 +39,7 @@ describe('subscribeToStateAndFetchBatches', () => { }) it('batches rapid subscription changes into single requests', async () => { - const subscription = subscribeToStateAndFetchBatches({instance, state}) + const subscription = subscribeToStateAndFetchBatches({instance, state, key}) const projection = '{title, description}' const projectionHash = hashString(projection) @@ -95,7 +96,7 @@ describe('subscribeToStateAndFetchBatches', () => { observable: new Observable(subscriber), } as StateSource) - const subscription = subscribeToStateAndFetchBatches({instance, state}) + const subscription = subscribeToStateAndFetchBatches({instance, state, key}) const projection = '{title}' const projectionHash = hashString(projection) @@ -166,7 +167,7 @@ describe('subscribeToStateAndFetchBatches', () => { subscriptions: {doc1: {[projectionHash]: {sub1: true}}}, }) - const subscription = subscribeToStateAndFetchBatches({instance, state}) + const subscription = subscribeToStateAndFetchBatches({instance, state, key}) // Add another subscription for doc1 (same hash) state.set('addSubscriptionAlreadyInBatch', (prev) => ({ @@ -218,7 +219,7 @@ describe('subscribeToStateAndFetchBatches', () => { it('cancels and restarts fetches when subscription set changes', async () => { const abortSpy = vi.spyOn(AbortController.prototype, 'abort') - const subscription = subscribeToStateAndFetchBatches({instance, state}) + const subscription = subscribeToStateAndFetchBatches({instance, state, key}) const projection = '{title, description}' const projectionHash = hashString(projection) const projection2 = '{_id}' // Different projection @@ -266,7 +267,7 @@ describe('subscribeToStateAndFetchBatches', () => { observable: new Observable(subscriber), } as StateSource) - const subscription = subscribeToStateAndFetchBatches({instance, state}) + const subscription = subscribeToStateAndFetchBatches({instance, state, key}) const projection = '{title, description}' const projectionHash = hashString(projection) diff --git a/packages/core/src/projection/subscribeToStateAndFetchBatches.ts b/packages/core/src/projection/subscribeToStateAndFetchBatches.ts index f0e98b40b..6bc2c102b 100644 --- a/packages/core/src/projection/subscribeToStateAndFetchBatches.ts +++ b/packages/core/src/projection/subscribeToStateAndFetchBatches.ts @@ -17,6 +17,7 @@ import { } from 'rxjs' import {getQueryState, resolveQuery} from '../query/queryStore' +import {type BoundDatasetKey} from '../store/createActionBinder' import {type StoreContext} from '../store/defineStore' import { createProjectionQuery, @@ -34,7 +35,8 @@ const isSetEqual = (a: Set, b: Set) => export const subscribeToStateAndFetchBatches = ({ state, instance, -}: StoreContext): Subscription => { + key: {projectId, dataset}, +}: StoreContext): Subscription => { const documentProjections$ = state.observable.pipe( map((s) => s.documentProjections), distinctUntilChanged(isEqual), @@ -94,6 +96,8 @@ export const subscribeToStateAndFetchBatches = ({ const {getCurrent, observable} = getQueryState(instance, { query, params, + projectId, + dataset, tag: PROJECTION_TAG, perspective: PROJECTION_PERSPECTIVE, }) @@ -104,6 +108,8 @@ export const subscribeToStateAndFetchBatches = ({ resolveQuery(instance, { query, params, + projectId, + dataset, tag: PROJECTION_TAG, perspective: PROJECTION_PERSPECTIVE, signal: controller.signal, @@ -125,8 +131,8 @@ export const subscribeToStateAndFetchBatches = ({ }), map(({ids, data}) => processProjectionQuery({ - projectId: instance.config.projectId!, - dataset: instance.config.dataset!, + projectId, + dataset, ids, results: data, }), diff --git a/packages/core/src/query/queryStore.ts b/packages/core/src/query/queryStore.ts index 4b59677ea..f563d1179 100644 --- a/packages/core/src/query/queryStore.ts +++ b/packages/core/src/query/queryStore.ts @@ -25,7 +25,7 @@ import { import {getClientState} from '../client/clientStore' import {type DatasetHandle} from '../config/sanityConfig' import {getPerspectiveState} from '../releases/getPerspectiveState' -import {bindActionByDataset} from '../store/createActionBinder' +import {bindActionByDataset, type BoundDatasetKey} from '../store/createActionBinder' import {type SanityInstance} from '../store/createSanityInstance' import { createStateSourceAction, @@ -104,7 +104,7 @@ function normalizeOptionsWithPerspective( } } -const queryStore = defineStore({ +const queryStore = defineStore({ name: 'QueryStore', getInitialState: () => ({queries: {}}), initialize(context) { @@ -125,7 +125,11 @@ const errorHandler = (state: StoreState<{error?: unknown}>) => { return (error: unknown): void => state.set('setError', {error}) } -const listenForNewSubscribersAndFetch = ({state, instance}: StoreContext) => { +const listenForNewSubscribersAndFetch = ({ + state, + instance, + key: {projectId, dataset}, +}: StoreContext) => { return state.observable .pipe( map((s) => new Set(Object.keys(s.queries))), @@ -157,8 +161,6 @@ const listenForNewSubscribersAndFetch = ({state, instance}: StoreContext) => { + key: {projectId, dataset}, +}: StoreContext) => { const liveMessages$ = getClientState(instance, { apiVersion: QUERY_STORE_API_VERSION, + projectId, + dataset, }).observable.pipe( switchMap((client) => defer(() => @@ -325,8 +332,11 @@ const _getQueryState = bindActionByDataset( * * @beta */ -export function getQueryErrorState(instance: SanityInstance): StateSource { - return _getQueryErrorState(instance) +export function getQueryErrorState( + instance: SanityInstance, + options: {projectId?: string; dataset?: string} = {}, +): StateSource { + return _getQueryErrorState(instance, options) } const _getQueryErrorState = bindActionByDataset( @@ -418,9 +428,11 @@ const _resolveQuery = bindActionByDataset( * Clears the top-level query store error. * @beta */ -export function clearQueryError(instance: SanityInstance): void -export function clearQueryError(...args: Parameters): void { - return _clearQueryError(...args) +export function clearQueryError( + instance: SanityInstance, + options: {projectId?: string; dataset?: string} = {}, +): void { + return _clearQueryError(instance, options) } const _clearQueryError = bindActionByDataset(queryStore, ({state}) => { diff --git a/packages/core/src/releases/getPerspectiveState.test.ts b/packages/core/src/releases/getPerspectiveState.test.ts index e79226b21..2f8f27739 100644 --- a/packages/core/src/releases/getPerspectiveState.test.ts +++ b/packages/core/src/releases/getPerspectiveState.test.ts @@ -54,7 +54,7 @@ describe('getPerspectiveState', () => { }) it('should return default perspective if no options or instance perspective is provided', async () => { - const state = getPerspectiveState(instance) + const state = getPerspectiveState(instance, {}) mockReleasesQuerySubject.next([]) const perspective = await firstValueFrom(state.observable) expect(perspective).toBe('drafts') @@ -62,7 +62,7 @@ describe('getPerspectiveState', () => { it('should return instance perspective if provided and no options perspective', async () => { instance.config.perspective = 'published' - const state = getPerspectiveState(instance) + const state = getPerspectiveState(instance, {}) mockReleasesQuerySubject.next([]) const perspective = await firstValueFrom(state.observable) expect(perspective).toBe('published') diff --git a/packages/core/src/releases/getPerspectiveState.ts b/packages/core/src/releases/getPerspectiveState.ts index 1ae3fb10a..ccaa46d04 100644 --- a/packages/core/src/releases/getPerspectiveState.ts +++ b/packages/core/src/releases/getPerspectiveState.ts @@ -23,7 +23,7 @@ const selectActiveReleases = (context: SelectorContext) => context.state.activeReleases const selectOptions = ( _context: SelectorContext, - options?: PerspectiveHandle, + options: PerspectiveHandle & {projectId?: string; dataset?: string}, ) => options const memoizedOptionsSelector = createSelector( diff --git a/packages/core/src/releases/releasesStore.test.ts b/packages/core/src/releases/releasesStore.test.ts index 938f2045a..7015dff81 100644 --- a/packages/core/src/releases/releasesStore.test.ts +++ b/packages/core/src/releases/releasesStore.test.ts @@ -58,7 +58,7 @@ describe('releasesStore', () => { vi.mocked(listenQuery).mockReturnValue(of(mockReleases)) - const state = getActiveReleasesState(instance) + const state = getActiveReleasesState(instance, {}) await new Promise((resolve) => setTimeout(resolve, 0)) @@ -72,7 +72,7 @@ describe('releasesStore', () => { const releasesSubject = new Subject() vi.mocked(listenQuery).mockReturnValue(releasesSubject.asObservable()) - const state = getActiveReleasesState(instance) + const state = getActiveReleasesState(instance, {}) // Initial state should be default expect(state.getCurrent()).toBeUndefined() // Default initial state @@ -116,7 +116,7 @@ describe('releasesStore', () => { // Configure listenQuery to return an empty array vi.mocked(listenQuery).mockReturnValue(of([])) - const state = getActiveReleasesState(instance) + const state = getActiveReleasesState(instance, {}) await new Promise((resolve) => setTimeout(resolve, 0)) @@ -127,7 +127,7 @@ describe('releasesStore', () => { it('should handle null/undefined from listenQuery by defaulting to empty array', async () => { // Test null case vi.mocked(listenQuery).mockReturnValue(of(null)) - const state = getActiveReleasesState(instance) + const state = getActiveReleasesState(instance, {}) await new Promise((resolve) => setTimeout(resolve, 0)) expect(state.getCurrent()).toEqual([]) expect(consoleErrorSpy).not.toHaveBeenCalled() @@ -146,7 +146,7 @@ describe('releasesStore', () => { vi.mocked(listenQuery).mockReturnValue(subject.asObservable()) // initialize the store - const state = getActiveReleasesState(instance) + const state = getActiveReleasesState(instance, {}) // Error the subject subject.error(error) diff --git a/packages/core/src/releases/releasesStore.ts b/packages/core/src/releases/releasesStore.ts index b6a7f4032..c06955c43 100644 --- a/packages/core/src/releases/releasesStore.ts +++ b/packages/core/src/releases/releasesStore.ts @@ -3,7 +3,7 @@ import {type SanityDocument} from '@sanity/types' import {catchError, EMPTY, retry, switchMap, timer} from 'rxjs' import {getClientState} from '../client/clientStore' -import {bindActionByDataset} from '../store/createActionBinder' +import {bindActionByDataset, type BoundDatasetKey} from '../store/createActionBinder' import {createStateSourceAction} from '../store/createStateSourceAction' import {defineStore, type StoreContext} from '../store/defineStore' import {listenQuery} from '../utils/listenQuery' @@ -32,7 +32,7 @@ export interface ReleasesStoreState { error?: unknown } -export const releasesStore = defineStore({ +export const releasesStore = defineStore({ name: 'Releases', getInitialState: (): ReleasesStoreState => ({ activeReleases: undefined, @@ -57,10 +57,16 @@ export const getActiveReleasesState = bindActionByDataset( const RELEASES_QUERY = 'releases::all()' const QUERY_PARAMS = {} -const subscribeToReleases = ({instance, state}: StoreContext) => { +const subscribeToReleases = ({ + instance, + state, + key: {projectId, dataset}, +}: StoreContext) => { return getClientState(instance, { apiVersion: '2025-04-10', perspective: 'raw', + projectId, + dataset, }) .observable.pipe( switchMap((client: SanityClient) => diff --git a/packages/core/src/store/createActionBinder.test.ts b/packages/core/src/store/createActionBinder.test.ts index 57bd46e95..cc942fbd9 100644 --- a/packages/core/src/store/createActionBinder.test.ts +++ b/packages/core/src/store/createActionBinder.test.ts @@ -12,7 +12,7 @@ beforeEach(() => vi.mocked(createStoreInstance).mockClear()) describe('createActionBinder', () => { it('should bind an action and call it with correct context and parameters, using caching', () => { - const binder = createActionBinder(() => '') + const binder = createActionBinder((..._rest) => ({name: ''})) const storeDefinition = { name: 'TestStore', getInitialState: () => ({counter: 0}), @@ -37,7 +37,9 @@ describe('createActionBinder', () => { }) it('should create separate store instances for different composite keys', () => { - const binder = createActionBinder(({projectId, dataset}) => `${projectId}.${dataset}`) + const binder = createActionBinder(({config: {projectId, dataset}}, ..._rest) => ({ + name: `${projectId}.${dataset}`, + })) const storeDefinition = { name: 'TestStore', getInitialState: () => ({counter: 0}), @@ -59,7 +61,7 @@ describe('createActionBinder', () => { }) it('should dispose the store instance when the last instance is disposed', () => { - const binder = createActionBinder(() => '') + const binder = createActionBinder((..._rest) => ({name: ''})) const storeDefinition = { name: 'TestStore', getInitialState: () => ({counter: 0}), @@ -93,10 +95,10 @@ describe('bindActionByDataset', () => { name: 'DSStore', getInitialState: () => ({counter: 0}), } - const action = vi.fn((_context, value: string) => value) + const action = vi.fn((_context, {value}: {value: string}) => value) const boundAction = bindActionByDataset(storeDefinition, action) const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'}) - const result = boundAction(instance, 'hello') + const result = boundAction(instance, {value: 'hello'}) expect(result).toBe('hello') }) @@ -109,7 +111,7 @@ describe('bindActionByDataset', () => { const boundAction = bindActionByDataset(storeDefinition, action) // Instance with missing dataset const instance = createSanityInstance({projectId: 'proj1', dataset: ''}) - expect(() => boundAction(instance)).toThrow( + expect(() => boundAction(instance, {})).toThrow( 'This API requires a project ID and dataset configured.', ) }) diff --git a/packages/core/src/store/createActionBinder.ts b/packages/core/src/store/createActionBinder.ts index 4d505707e..3648c3712 100644 --- a/packages/core/src/store/createActionBinder.ts +++ b/packages/core/src/store/createActionBinder.ts @@ -1,14 +1,19 @@ -import {type SanityConfig} from '../config/sanityConfig' import {type SanityInstance} from './createSanityInstance' import {createStoreInstance, type StoreInstance} from './createStoreInstance' import {type StoreState} from './createStoreState' import {type StoreContext, type StoreDefinition} from './defineStore' +export type BoundDatasetKey = { + name: string + projectId: string + dataset: string +} + /** * Defines a store action that operates on a specific state type */ -export type StoreAction = ( - context: StoreContext, +export type StoreAction = ( + context: StoreContext, ...params: TParams ) => TReturn @@ -43,7 +48,10 @@ export type BoundStoreAction<_TState, TParams extends unknown[], TReturn> = ( * ) * ``` */ -export function createActionBinder(keyFn: (config: SanityConfig) => string) { +export function createActionBinder< + TKey extends {name: string}, + TKeyParams extends unknown[] = unknown[], +>(keyFn: (instance: SanityInstance, ...params: TKeyParams) => TKey) { const instanceRegistry = new Map>() const storeRegistry = new Map>() @@ -54,13 +62,13 @@ export function createActionBinder(keyFn: (config: SanityConfig) => string) { * @param action - The action to bind * @returns A function that executes the action with a Sanity instance */ - return function bindAction( - storeDefinition: StoreDefinition, - action: StoreAction, + return function bindAction( + storeDefinition: StoreDefinition, + action: StoreAction, ): BoundStoreAction { return function boundAction(instance: SanityInstance, ...params: TParams) { - const keySuffix = keyFn(instance.config) - const compositeKey = storeDefinition.name + (keySuffix ? `:${keySuffix}` : '') + const key = keyFn(instance, ...params) + const compositeKey = storeDefinition.name + (key.name ? `:${key.name}` : '') // Get or create instance set for this composite key let instances = instanceRegistry.get(compositeKey) @@ -87,12 +95,12 @@ export function createActionBinder(keyFn: (config: SanityConfig) => string) { // Get or create store instance let storeInstance = storeRegistry.get(compositeKey) if (!storeInstance) { - storeInstance = createStoreInstance(instance, storeDefinition) + storeInstance = createStoreInstance(instance, key, storeDefinition) storeRegistry.set(compositeKey, storeInstance) } // Execute action with store context - return action({instance, state: storeInstance.state as StoreState}, ...params) + return action({instance, state: storeInstance.state as StoreState, key}, ...params) } } } @@ -128,11 +136,16 @@ export function createActionBinder(keyFn: (config: SanityConfig) => string) { * fetchDocument(sanityInstance, 'doc123') * ``` */ -export const bindActionByDataset = createActionBinder(({projectId, dataset}) => { +export const bindActionByDataset = createActionBinder< + BoundDatasetKey, + [object & {projectId?: string; dataset?: string}] +>((instance, options) => { + const projectId = options.projectId ?? instance.config.projectId + const dataset = options.dataset ?? instance.config.dataset if (!projectId || !dataset) { throw new Error('This API requires a project ID and dataset configured.') } - return `${projectId}.${dataset}` + return {name: `${projectId}.${dataset}`, projectId, dataset} }) /** @@ -173,4 +186,4 @@ export const bindActionByDataset = createActionBinder(({projectId, dataset}) => * getCurrentUser(sanityInstance) * ``` */ -export const bindActionGlobally = createActionBinder(() => 'global') +export const bindActionGlobally = createActionBinder((..._rest) => ({name: 'global'})) diff --git a/packages/core/src/store/createStateSourceAction.test.ts b/packages/core/src/store/createStateSourceAction.test.ts index 00320cea7..4b673ac8d 100644 --- a/packages/core/src/store/createStateSourceAction.test.ts +++ b/packages/core/src/store/createStateSourceAction.test.ts @@ -21,7 +21,7 @@ describe('createStateSourceAction', () => { it('should create a source that provides current state through getCurrent', () => { const selector = vi.fn(({state: s}: SelectorContext) => s.count) const action = createStateSourceAction(selector) - const source = action({state, instance}) + const source = action({state, instance, key: null}) expect(source.getCurrent()).toBe(0) state.set('test', {count: 5}) @@ -33,7 +33,7 @@ describe('createStateSourceAction', () => { const source = createStateSourceAction({ selector: ({state: s}: SelectorContext) => s.count, isEqual: (a, b) => a === b, - })({state, instance}) + })({state, instance, key: null}) const unsubscribe = source.subscribe(onStoreChanged) @@ -53,11 +53,11 @@ describe('createStateSourceAction', () => { const source = createStateSourceAction({ selector: ({state: s}: SelectorContext) => s.items, onSubscribe, - })({state, instance}) + })({state, instance, key: null}) const unsubscribe = source.subscribe() expect(onSubscribe).toHaveBeenCalledWith( - expect.objectContaining({state, instance}), + expect.objectContaining({state, instance, key: null}), // No params in this case ) @@ -68,7 +68,7 @@ describe('createStateSourceAction', () => { const action = createStateSourceAction({ selector: ({state: s}: SelectorContext, index: number) => s.items[index], }) - const source = action({state, instance}, 0) + const source = action({state, instance, key: null}, 0) state.set('add', {items: ['first']}) expect(source.getCurrent()).toBe('first') @@ -80,7 +80,7 @@ describe('createStateSourceAction', () => { selector: () => { throw error }, - })({state, instance}) + })({state, instance, key: null}) const errorHandler = vi.fn() source.observable.subscribe({error: errorHandler}) @@ -94,7 +94,7 @@ describe('createStateSourceAction', () => { const source = createStateSourceAction({ selector: ({state: s}: SelectorContext) => s.items.map((i) => i.length), isEqual, - })({state, instance}) + })({state, instance, key: null}) const onChange = vi.fn() source.subscribe(onChange) @@ -112,7 +112,7 @@ describe('createStateSourceAction', () => { const source = createStateSourceAction({ selector: ({state: s}: SelectorContext) => s.count, onSubscribe: () => cleanup, - })({state, instance}) + })({state, instance, key: null}) const unsubscribe = source.subscribe() unsubscribe() @@ -125,6 +125,7 @@ describe('createStateSourceAction', () => { )({ state, instance, + key: null, }) const subscriber1 = vi.fn() @@ -144,7 +145,7 @@ describe('createStateSourceAction', () => { it('should cache selector context per state object', () => { const selector = vi.fn(({state: s}: SelectorContext) => s.count) - const source = createStateSourceAction(selector)({state, instance}) + const source = createStateSourceAction(selector)({state, instance, key: null}) // Initial call creates context expect(source.getCurrent()).toBe(0) @@ -181,10 +182,10 @@ describe('createStateSourceAction', () => { const secondInstance = createSanityInstance({projectId: 'test2', dataset: 'test2'}) const selector = vi.fn(({state: s}: SelectorContext) => s.count) - const source1 = createStateSourceAction(selector)({state, instance}) + const source1 = createStateSourceAction(selector)({state, instance, key: null}) source1.getCurrent() - const source2 = createStateSourceAction(selector)({state, instance: secondInstance}) + const source2 = createStateSourceAction(selector)({state, instance: secondInstance, key: null}) source2.getCurrent() const context1 = selector.mock.calls[0][0] diff --git a/packages/core/src/store/createStateSourceAction.ts b/packages/core/src/store/createStateSourceAction.ts index 1449ef976..234670561 100644 --- a/packages/core/src/store/createStateSourceAction.ts +++ b/packages/core/src/store/createStateSourceAction.ts @@ -89,7 +89,7 @@ export type Selector = ( /** * Configuration options for creating a state source action */ -interface StateSourceOptions { +interface StateSourceOptions { /** * Selector function that derives the desired value from store state * @@ -106,7 +106,7 @@ interface StateSourceOptions { * @param params - Action parameters provided during invocation * @returns Optional cleanup function called when subscription ends */ - onSubscribe?: (context: StoreContext, ...params: TParams) => void | (() => void) + onSubscribe?: (context: StoreContext, ...params: TParams) => void | (() => void) /** * Equality function to prevent unnecessary updates @@ -168,9 +168,9 @@ interface StateSourceOptions { * }) * ``` */ -export function createStateSourceAction( - options: Selector | StateSourceOptions, -): StoreAction> { +export function createStateSourceAction( + options: Selector | StateSourceOptions, +): StoreAction, TKey> { const selector = typeof options === 'function' ? options : options.selector const subscribeHandler = options && 'onSubscribe' in options ? options.onSubscribe : undefined const isEqual = options && 'isEqual' in options ? (options.isEqual ?? Object.is) : Object.is @@ -184,7 +184,7 @@ export function createStateSourceAction, ...params: TParams) { + function stateSourceAction(context: StoreContext, ...params: TParams) { const {state, instance} = context const getCurrent = () => { diff --git a/packages/core/src/store/createStoreInstance.test.ts b/packages/core/src/store/createStoreInstance.test.ts index 1a08a2341..67084c5a8 100644 --- a/packages/core/src/store/createStoreInstance.test.ts +++ b/packages/core/src/store/createStoreInstance.test.ts @@ -24,36 +24,45 @@ describe('createStoreInstance', () => { } it('should create store instance with initial state', () => { - const store = createStoreInstance(instance, storeDef) + const store = createStoreInstance(instance, {name: 'store'}, storeDef) expect(store.state).toBeDefined() }) it('should call getInitialState with Sanity instance', () => { const getInitialState = vi.fn(() => ({count: 0})) - createStoreInstance(instance, {...storeDef, getInitialState}) - expect(getInitialState).toHaveBeenCalledWith(instance) + createStoreInstance(instance, {name: 'store'}, {...storeDef, getInitialState}) + expect(getInitialState).toHaveBeenCalledWith(instance, {name: 'store'}) }) it('should call initialize function with context', () => { const initialize = vi.fn() - const store = createStoreInstance(instance, { - ...storeDef, - initialize, - }) + const store = createStoreInstance( + instance, + {name: 'store'}, + { + ...storeDef, + initialize, + }, + ) expect(initialize).toHaveBeenCalledWith({ state: store.state, instance, + key: {name: 'store'}, }) }) it('should handle store disposal with cleanup function', () => { const disposeMock = vi.fn() - const store = createStoreInstance(instance, { - ...storeDef, - initialize: () => disposeMock, - }) + const store = createStoreInstance( + instance, + {name: 'store'}, + { + ...storeDef, + initialize: () => disposeMock, + }, + ) store.dispose() expect(disposeMock).toHaveBeenCalledTimes(1) @@ -61,7 +70,7 @@ describe('createStoreInstance', () => { }) it('should handle disposal without initialize function', () => { - const store = createStoreInstance(instance, storeDef) + const store = createStoreInstance(instance, {name: 'store'}, storeDef) store.dispose() expect(store.isDisposed()).toBe(true) }) @@ -69,10 +78,14 @@ describe('createStoreInstance', () => { it('should prevent multiple disposals', () => { const disposeMock = vi.fn() - const store = createStoreInstance(instance, { - ...storeDef, - initialize: () => disposeMock, - }) + const store = createStoreInstance( + instance, + {name: 'store'}, + { + ...storeDef, + initialize: () => disposeMock, + }, + ) store.dispose() store.dispose() diff --git a/packages/core/src/store/createStoreInstance.ts b/packages/core/src/store/createStoreInstance.ts index db1c50d21..073ad9826 100644 --- a/packages/core/src/store/createStoreInstance.ts +++ b/packages/core/src/store/createStoreInstance.ts @@ -57,15 +57,16 @@ export interface StoreInstance { * instance.dispose() * ``` */ -export function createStoreInstance( +export function createStoreInstance( instance: SanityInstance, - {name, getInitialState, initialize}: StoreDefinition, + key: TKey, + {name, getInitialState, initialize}: StoreDefinition, ): StoreInstance { - const state = createStoreState(getInitialState(instance), { + const state = createStoreState(getInitialState(instance, key), { enabled: !!getEnv('DEV'), - name: `${name}-${instance.config.projectId}.${instance.config.dataset}`, + name: `${name}-${key.name}`, }) - const dispose = initialize?.({state, instance}) + const dispose = initialize?.({state, instance, key}) const disposed = {current: false} return { diff --git a/packages/core/src/store/defineStore.test.ts b/packages/core/src/store/defineStore.test.ts index 30a4524d9..cb075fd5d 100644 --- a/packages/core/src/store/defineStore.test.ts +++ b/packages/core/src/store/defineStore.test.ts @@ -13,6 +13,6 @@ describe('defineStore', () => { const result = defineStore(storeDef) expect(result).toBe(storeDef) expect(result.name).toBe('TestStore') - expect(result.getInitialState({} as SanityInstance)).toBe(42) + expect(result.getInitialState({} as SanityInstance, null)).toBe(42) }) }) diff --git a/packages/core/src/store/defineStore.ts b/packages/core/src/store/defineStore.ts index d7c20ee8c..c3983fb96 100644 --- a/packages/core/src/store/defineStore.ts +++ b/packages/core/src/store/defineStore.ts @@ -4,7 +4,7 @@ import {type StoreState} from './createStoreState' /** * Context object provided to store initialization functions */ -export interface StoreContext { +export interface StoreContext { /** * Sanity instance associated with this store * @@ -20,6 +20,11 @@ export interface StoreContext { * Contains methods for getting/setting state and observing changes */ state: StoreState + + /** + * The key used to instantiate the store. + */ + key: TKey } /** @@ -29,7 +34,7 @@ export interface StoreContext { * Stores are isolated state containers that can be associated with Sanity instances. * Each store definition creates a separate state instance per composite key. */ -export interface StoreDefinition { +export interface StoreDefinition { /** * Unique name for the store * @@ -47,7 +52,7 @@ export interface StoreDefinition { * Called when a new store instance is created. Can use Sanity instance * configuration to determine initial state. */ - getInitialState: (instance: SanityInstance) => TState + getInitialState: (instance: SanityInstance, key: TKey) => TState /** * Optional initialization function @@ -65,7 +70,7 @@ export interface StoreDefinition { * - Cancel pending operations * - Dispose external connections */ - initialize?: (context: StoreContext) => (() => void) | undefined + initialize?: (context: StoreContext) => (() => void) | undefined } /** @@ -74,8 +79,8 @@ export interface StoreDefinition { * @param storeDefinition - Configuration object defining the store * @returns The finalized store definition */ -export function defineStore( - storeDefinition: StoreDefinition, -): StoreDefinition { +export function defineStore( + storeDefinition: StoreDefinition, +): StoreDefinition { return storeDefinition } diff --git a/packages/react/src/hooks/presence/usePresence.ts b/packages/react/src/hooks/presence/usePresence.ts index c94df5dc1..f4bd1dce2 100644 --- a/packages/react/src/hooks/presence/usePresence.ts +++ b/packages/react/src/hooks/presence/usePresence.ts @@ -11,7 +11,7 @@ export function usePresence(): { locations: UserPresence[] } { const sanityInstance = useSanityInstance() - const source = useMemo(() => getPresence(sanityInstance), [sanityInstance]) + const source = useMemo(() => getPresence(sanityInstance, {}), [sanityInstance]) const subscribe = useCallback((callback: () => void) => source.subscribe(callback), [source]) const locations = useSyncExternalStore( subscribe, diff --git a/packages/react/src/hooks/releases/useActiveReleases.ts b/packages/react/src/hooks/releases/useActiveReleases.ts index 539feffa0..ced6eeb5b 100644 --- a/packages/react/src/hooks/releases/useActiveReleases.ts +++ b/packages/react/src/hooks/releases/useActiveReleases.ts @@ -33,7 +33,7 @@ type UseActiveReleases = { export const useActiveReleases: UseActiveReleases = createStateSourceHook({ getState: getActiveReleasesState as (instance: SanityInstance) => StateSource, shouldSuspend: (instance: SanityInstance) => - getActiveReleasesState(instance).getCurrent() === undefined, + getActiveReleasesState(instance, {}).getCurrent() === undefined, suspender: (instance: SanityInstance) => - firstValueFrom(getActiveReleasesState(instance).observable.pipe(filter(Boolean))), + firstValueFrom(getActiveReleasesState(instance, {}).observable.pipe(filter(Boolean))), }) diff --git a/packages/react/src/hooks/releases/usePerspective.ts b/packages/react/src/hooks/releases/usePerspective.ts index 92cc544b3..568ff0d4e 100644 --- a/packages/react/src/hooks/releases/usePerspective.ts +++ b/packages/react/src/hooks/releases/usePerspective.ts @@ -43,8 +43,8 @@ export const usePerspective: UsePerspective = createStateSourceHook({ instance: SanityInstance, perspectiveHandle?: PerspectiveHandle, ) => StateSource, - shouldSuspend: (instance: SanityInstance, options?: PerspectiveHandle): boolean => + shouldSuspend: (instance: SanityInstance, options: PerspectiveHandle): boolean => getPerspectiveState(instance, options).getCurrent() === undefined, suspender: (instance: SanityInstance, _options?: PerspectiveHandle) => - firstValueFrom(getActiveReleasesState(instance).observable.pipe(filter(Boolean))), + firstValueFrom(getActiveReleasesState(instance, {}).observable.pipe(filter(Boolean))), })