@sanity/sdk 0.0.0-alpha.21 → 0.0.0-alpha.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +428 -325
- package/dist/index.js +1618 -1553
- package/dist/index.js.map +1 -1
- package/package.json +6 -7
- package/src/_exports/index.ts +31 -30
- package/src/auth/authStore.test.ts +149 -104
- package/src/auth/authStore.ts +51 -100
- package/src/auth/handleAuthCallback.test.ts +67 -34
- package/src/auth/handleAuthCallback.ts +8 -7
- package/src/auth/logout.test.ts +61 -29
- package/src/auth/logout.ts +26 -28
- package/src/auth/refreshStampedToken.test.ts +9 -9
- package/src/auth/refreshStampedToken.ts +62 -56
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +5 -5
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +45 -47
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -5
- package/src/auth/subscribeToStorageEventsAndSetToken.ts +22 -24
- package/src/client/clientStore.test.ts +131 -67
- package/src/client/clientStore.ts +117 -116
- package/src/comlink/controller/actions/destroyController.test.ts +38 -13
- package/src/comlink/controller/actions/destroyController.ts +11 -15
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +56 -27
- package/src/comlink/controller/actions/getOrCreateChannel.ts +37 -35
- package/src/comlink/controller/actions/getOrCreateController.test.ts +27 -16
- package/src/comlink/controller/actions/getOrCreateController.ts +23 -22
- package/src/comlink/controller/actions/releaseChannel.test.ts +37 -13
- package/src/comlink/controller/actions/releaseChannel.ts +22 -21
- package/src/comlink/controller/comlinkControllerStore.test.ts +65 -36
- package/src/comlink/controller/comlinkControllerStore.ts +44 -5
- package/src/comlink/node/actions/getOrCreateNode.test.ts +31 -15
- package/src/comlink/node/actions/getOrCreateNode.ts +30 -29
- package/src/comlink/node/actions/releaseNode.test.ts +75 -55
- package/src/comlink/node/actions/releaseNode.ts +19 -21
- package/src/comlink/node/comlinkNodeStore.test.ts +6 -11
- package/src/comlink/node/comlinkNodeStore.ts +22 -5
- package/src/config/authConfig.ts +79 -0
- package/src/config/sanityConfig.ts +48 -0
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +18 -5
- package/src/document/actions.test.ts +22 -10
- package/src/document/actions.ts +44 -56
- package/src/document/applyDocumentActions.test.ts +96 -36
- package/src/document/applyDocumentActions.ts +140 -99
- package/src/document/documentStore.test.ts +103 -155
- package/src/document/documentStore.ts +247 -237
- package/src/document/listen.ts +56 -55
- package/src/document/patchOperations.ts +0 -43
- package/src/document/permissions.test.ts +25 -12
- package/src/document/permissions.ts +11 -4
- package/src/document/processActions.test.ts +41 -8
- package/src/document/reducers.test.ts +87 -16
- package/src/document/reducers.ts +2 -2
- package/src/document/sharedListener.test.ts +34 -16
- package/src/document/sharedListener.ts +33 -11
- package/src/preview/getPreviewState.test.ts +40 -39
- package/src/preview/getPreviewState.ts +68 -56
- package/src/preview/previewConstants.ts +43 -0
- package/src/preview/previewQuery.test.ts +1 -1
- package/src/preview/previewQuery.ts +4 -5
- package/src/preview/previewStore.test.ts +13 -58
- package/src/preview/previewStore.ts +7 -21
- package/src/preview/resolvePreview.test.ts +33 -104
- package/src/preview/resolvePreview.ts +11 -21
- package/src/preview/subscribeToStateAndFetchBatches.test.ts +96 -97
- package/src/preview/subscribeToStateAndFetchBatches.ts +85 -81
- package/src/preview/util.ts +1 -0
- package/src/project/project.test.ts +3 -3
- package/src/project/project.ts +28 -5
- package/src/projection/getProjectionState.test.ts +69 -49
- package/src/projection/getProjectionState.ts +42 -50
- package/src/projection/projectionQuery.ts +1 -1
- package/src/projection/projectionStore.test.ts +13 -51
- package/src/projection/projectionStore.ts +6 -18
- package/src/projection/resolveProjection.test.ts +32 -127
- package/src/projection/resolveProjection.ts +15 -28
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +105 -90
- package/src/projection/subscribeToStateAndFetchBatches.ts +94 -81
- package/src/projection/util.ts +2 -0
- package/src/projects/projects.test.ts +13 -4
- package/src/projects/projects.ts +6 -1
- package/src/query/queryStore.test.ts +10 -47
- package/src/query/queryStore.ts +151 -133
- package/src/query/queryStoreConstants.ts +2 -0
- package/src/store/createActionBinder.test.ts +153 -0
- package/src/store/createActionBinder.ts +176 -0
- package/src/store/createSanityInstance.test.ts +84 -0
- package/src/store/createSanityInstance.ts +124 -0
- package/src/store/createStateSourceAction.test.ts +196 -0
- package/src/store/createStateSourceAction.ts +260 -0
- package/src/store/createStoreInstance.test.ts +81 -0
- package/src/store/createStoreInstance.ts +80 -0
- package/src/store/createStoreState.test.ts +85 -0
- package/src/store/createStoreState.ts +92 -0
- package/src/store/defineStore.test.ts +18 -0
- package/src/store/defineStore.ts +81 -0
- package/src/users/reducers.test.ts +318 -0
- package/src/users/reducers.ts +88 -0
- package/src/users/types.ts +46 -4
- package/src/users/usersConstants.ts +4 -0
- package/src/users/usersStore.test.ts +350 -223
- package/src/users/usersStore.ts +285 -149
- package/src/utils/createFetcherStore.test.ts +6 -7
- package/src/utils/createFetcherStore.ts +150 -153
- package/src/{common/util.test.ts → utils/hashString.test.ts} +1 -1
- package/src/auth/fetchLoginUrls.test.ts +0 -163
- package/src/auth/fetchLoginUrls.ts +0 -74
- package/src/common/createLiveEventSubscriber.test.ts +0 -121
- package/src/common/createLiveEventSubscriber.ts +0 -55
- package/src/common/types.ts +0 -4
- package/src/instance/identity.test.ts +0 -46
- package/src/instance/identity.ts +0 -29
- package/src/instance/sanityInstance.test.ts +0 -77
- package/src/instance/sanityInstance.ts +0 -57
- package/src/instance/types.ts +0 -37
- package/src/preview/getPreviewProjection.ts +0 -45
- package/src/resources/README.md +0 -370
- package/src/resources/createAction.test.ts +0 -101
- package/src/resources/createAction.ts +0 -44
- package/src/resources/createResource.test.ts +0 -112
- package/src/resources/createResource.ts +0 -102
- package/src/resources/createStateSourceAction.test.ts +0 -114
- package/src/resources/createStateSourceAction.ts +0 -83
- package/src/resources/createStore.test.ts +0 -67
- package/src/resources/createStore.ts +0 -46
- package/src/store/createStore.test.ts +0 -108
- package/src/store/createStore.ts +0 -106
- /package/src/{common/util.ts → utils/hashString.ts} +0 -0
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import {Observable} from 'rxjs'
|
|
2
|
-
import {devtools, type DevtoolsOptions} from 'zustand/middleware'
|
|
3
|
-
import {createStore} from 'zustand/vanilla'
|
|
4
|
-
|
|
5
|
-
import {type SanityInstance, type SdkIdentity} from '../instance/types'
|
|
6
|
-
import {getEnv} from '../utils/getEnv'
|
|
7
|
-
|
|
8
|
-
const resourceCache = new WeakMap<SdkIdentity, Map<string, InitializedResource<unknown>>>()
|
|
9
|
-
|
|
10
|
-
type Teardown = () => void
|
|
11
|
-
|
|
12
|
-
export interface Resource<TState> {
|
|
13
|
-
name: string
|
|
14
|
-
getInitialState(instance: SanityInstance): TState
|
|
15
|
-
initialize?: (
|
|
16
|
-
this: {instance: SanityInstance; state: ResourceState<TState>},
|
|
17
|
-
instance: SanityInstance,
|
|
18
|
-
) => Teardown
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function createResource<TState>(resource: Resource<TState>): Resource<TState> {
|
|
22
|
-
return resource
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @public
|
|
27
|
-
*/
|
|
28
|
-
export type ResourceState<TState> = {
|
|
29
|
-
get: () => TState
|
|
30
|
-
set: (name: string, state: Partial<TState> | ((s: TState) => Partial<TState>)) => void
|
|
31
|
-
observable: Observable<TState>
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface InitializedResource<TState> {
|
|
35
|
-
state: ResourceState<TState>
|
|
36
|
-
dispose: () => void
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function createResourceState<TState>(
|
|
40
|
-
initialState: TState,
|
|
41
|
-
devToolsOptions?: DevtoolsOptions,
|
|
42
|
-
): ResourceState<TState> {
|
|
43
|
-
const store = createStore<TState>()(devtools(() => initialState, devToolsOptions))
|
|
44
|
-
return {
|
|
45
|
-
get: store.getState,
|
|
46
|
-
set: (actionKey, updatedState) => {
|
|
47
|
-
// avoids unnecessary updates if the state remains unchanged. since we
|
|
48
|
-
// use immutable data, this is safe and aligns with React's approach
|
|
49
|
-
if (store.getState() !== updatedState) {
|
|
50
|
-
store.setState(updatedState, false, actionKey)
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
observable: new Observable((observer) => {
|
|
54
|
-
const emit = () => observer.next(store.getState())
|
|
55
|
-
emit()
|
|
56
|
-
return store.subscribe(emit)
|
|
57
|
-
}),
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function initializeResource<TState>(
|
|
62
|
-
instance: SanityInstance,
|
|
63
|
-
resource: Resource<TState>,
|
|
64
|
-
): InitializedResource<TState> {
|
|
65
|
-
const fullName =
|
|
66
|
-
resource.name === 'Auth' ? 'Auth-global' : `${resource.name}-${instance.identity.resourceId}`
|
|
67
|
-
const initialState = resource.getInitialState(instance)
|
|
68
|
-
const state = createResourceState(initialState, {
|
|
69
|
-
name: fullName,
|
|
70
|
-
enabled: !!getEnv('DEV'),
|
|
71
|
-
})
|
|
72
|
-
const dispose = resource.initialize?.call({instance, state}, instance) ?? (() => {})
|
|
73
|
-
|
|
74
|
-
return {state, dispose}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function getOrCreateResource<TState>(
|
|
78
|
-
instance: SanityInstance,
|
|
79
|
-
resource: Resource<TState>,
|
|
80
|
-
): InitializedResource<TState> {
|
|
81
|
-
const fullName =
|
|
82
|
-
resource.name === 'Auth' ? 'Auth-global' : `${resource.name}-${instance.identity.resourceId}`
|
|
83
|
-
if (!resourceCache.has(instance.identity)) {
|
|
84
|
-
resourceCache.set(instance.identity, new Map())
|
|
85
|
-
}
|
|
86
|
-
const initializedResources = resourceCache.get(instance.identity)!
|
|
87
|
-
const cached = initializedResources.get(fullName)
|
|
88
|
-
if (cached) return cached as InitializedResource<TState>
|
|
89
|
-
|
|
90
|
-
const result = initializeResource(instance, resource)
|
|
91
|
-
initializedResources.set(fullName, result)
|
|
92
|
-
return result
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function disposeResources(identity: SdkIdentity): void {
|
|
96
|
-
const resources = resourceCache.get(identity)
|
|
97
|
-
if (!resources) return
|
|
98
|
-
|
|
99
|
-
for (const resource of resources.values()) {
|
|
100
|
-
resource.dispose()
|
|
101
|
-
}
|
|
102
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {createSanityInstance} from '../instance/sanityInstance'
|
|
4
|
-
import {createAction} from './createAction'
|
|
5
|
-
import {createResource} from './createResource'
|
|
6
|
-
import {createStateSourceAction} from './createStateSourceAction'
|
|
7
|
-
|
|
8
|
-
describe('createStateSourceAction', () => {
|
|
9
|
-
const resource = createResource({
|
|
10
|
-
name: 'testResource',
|
|
11
|
-
getInitialState: () => ({value: 10, subscribed: false}),
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
const setValue = createAction(resource, ({state}) => {
|
|
15
|
-
return function (value: number) {
|
|
16
|
-
state.set('updateValue', {value})
|
|
17
|
-
}
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
const getValueState = createStateSourceAction(resource, {
|
|
21
|
-
selector: (state) => state.value,
|
|
22
|
-
onSubscribe: ({state}) => {
|
|
23
|
-
state.set('setSubscribedTrue', {subscribed: true})
|
|
24
|
-
|
|
25
|
-
return () => {
|
|
26
|
-
state.set('setSubscribedFalse', {subscribed: false})
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
const getSubscribed = createAction(resource, ({state}) => {
|
|
32
|
-
return function () {
|
|
33
|
-
return state.get().subscribed
|
|
34
|
-
}
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('should return the current value', () => {
|
|
38
|
-
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
39
|
-
const valueState = getValueState(instance)
|
|
40
|
-
|
|
41
|
-
expect(valueState.getCurrent()).toBe(10)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('should subscribe to state changes and call the provided function', () => {
|
|
45
|
-
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
46
|
-
const valueState = getValueState(instance)
|
|
47
|
-
|
|
48
|
-
const mockCallback = vi.fn()
|
|
49
|
-
valueState.subscribe(mockCallback)
|
|
50
|
-
|
|
51
|
-
setValue(instance, 5)
|
|
52
|
-
|
|
53
|
-
expect(mockCallback).toHaveBeenCalledTimes(1)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('should unsubscribe from the state changes', () => {
|
|
57
|
-
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
58
|
-
const valueState = getValueState(instance)
|
|
59
|
-
const mockCallback = vi.fn()
|
|
60
|
-
|
|
61
|
-
const unsubscribe = valueState.subscribe(mockCallback)
|
|
62
|
-
unsubscribe()
|
|
63
|
-
|
|
64
|
-
setValue(instance, 5)
|
|
65
|
-
expect(mockCallback).toHaveBeenCalledTimes(0)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('should emit only when the value actually changes', () => {
|
|
69
|
-
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
70
|
-
const valueState = getValueState(instance)
|
|
71
|
-
|
|
72
|
-
const mockCallback = vi.fn()
|
|
73
|
-
valueState.subscribe(mockCallback)
|
|
74
|
-
|
|
75
|
-
setValue(instance, valueState.getCurrent())
|
|
76
|
-
expect(mockCallback).toHaveBeenCalledTimes(0)
|
|
77
|
-
|
|
78
|
-
setValue(instance, 5)
|
|
79
|
-
expect(mockCallback).toHaveBeenCalledTimes(1)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('returns an observable that emits the current value on subscribe', () => {
|
|
83
|
-
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
84
|
-
const valueState = getValueState(instance)
|
|
85
|
-
|
|
86
|
-
const observer = vi.fn()
|
|
87
|
-
const subscription = valueState.observable.subscribe(observer)
|
|
88
|
-
|
|
89
|
-
expect(observer).toHaveBeenCalledTimes(1)
|
|
90
|
-
expect(observer).toHaveBeenCalledWith(10)
|
|
91
|
-
|
|
92
|
-
// try a no-op change
|
|
93
|
-
setValue(instance, valueState.getCurrent())
|
|
94
|
-
expect(observer).toHaveBeenCalledTimes(1)
|
|
95
|
-
|
|
96
|
-
// set a value that will change
|
|
97
|
-
setValue(instance, 5)
|
|
98
|
-
expect(observer).toHaveBeenCalledTimes(2)
|
|
99
|
-
|
|
100
|
-
subscription.unsubscribe()
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('allows providing an `onSubscribe` handler', () => {
|
|
104
|
-
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
105
|
-
const valueState = getValueState(instance)
|
|
106
|
-
|
|
107
|
-
expect(getSubscribed(instance)).toBe(false)
|
|
108
|
-
const unsubscribe = valueState.subscribe()
|
|
109
|
-
expect(getSubscribed(instance)).toBe(true)
|
|
110
|
-
|
|
111
|
-
unsubscribe()
|
|
112
|
-
expect(getSubscribed(instance)).toBe(false)
|
|
113
|
-
})
|
|
114
|
-
})
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import {distinctUntilChanged, map, Observable, share, skip} from 'rxjs'
|
|
2
|
-
|
|
3
|
-
import {type ActionContext, createAction, type ResourceAction} from './createAction'
|
|
4
|
-
import {type Resource} from './createResource'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @public
|
|
8
|
-
*/
|
|
9
|
-
export interface StateSource<T> {
|
|
10
|
-
subscribe: (onStoreChanged?: () => void) => () => void
|
|
11
|
-
getCurrent: () => T
|
|
12
|
-
observable: Observable<T>
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type Selector<TState, TParams extends unknown[], TReturn> = (
|
|
16
|
-
state: TState,
|
|
17
|
-
...params: TParams
|
|
18
|
-
) => TReturn
|
|
19
|
-
interface StateSourceOptions<TState, TParams extends unknown[], TReturn> {
|
|
20
|
-
selector: Selector<TState, TParams, TReturn>
|
|
21
|
-
onSubscribe?: (context: ActionContext<TState>, ...params: TParams) => void | (() => void)
|
|
22
|
-
isEqual?: (prev: TReturn, curr: TReturn) => boolean
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function createStateSourceAction<TState, TParams extends unknown[], TReturn>(
|
|
26
|
-
resource: Resource<TState>,
|
|
27
|
-
options: Selector<TState, TParams, TReturn> | StateSourceOptions<TState, TParams, TReturn>,
|
|
28
|
-
): ResourceAction<TState, TParams, StateSource<TReturn>> {
|
|
29
|
-
const selector = typeof options === 'function' ? options : options.selector
|
|
30
|
-
const subscribeHandler = options && 'onSubscribe' in options ? options.onSubscribe : undefined
|
|
31
|
-
const isEqual = options && 'isEqual' in options ? (options.isEqual ?? Object.is) : Object.is
|
|
32
|
-
|
|
33
|
-
return createAction(resource, ({state}) => {
|
|
34
|
-
return function (...args: TParams): StateSource<TReturn> {
|
|
35
|
-
const getCurrent = () => selector(state.get(), ...args)
|
|
36
|
-
|
|
37
|
-
const subscribe = (onStoreChanged?: () => void) => {
|
|
38
|
-
const cleanup = subscribeHandler?.(this, ...args)
|
|
39
|
-
|
|
40
|
-
const subscription = state.observable
|
|
41
|
-
.pipe(
|
|
42
|
-
map(getCurrent),
|
|
43
|
-
distinctUntilChanged(isEqual),
|
|
44
|
-
// skip the first emission because we only want to emit when the
|
|
45
|
-
// value changes. `distinctUntilChanged` will always emit the first
|
|
46
|
-
// the first value so we skip this emission
|
|
47
|
-
skip(1),
|
|
48
|
-
)
|
|
49
|
-
.subscribe({
|
|
50
|
-
next: () => onStoreChanged?.(),
|
|
51
|
-
// the convention is to have the selector throw the error so we
|
|
52
|
-
// invoke onStoreChanged on error as well. this will cause the
|
|
53
|
-
// observable code path below to emit an error because the selector
|
|
54
|
-
// will throw and that will be used to emit an .error on the observer
|
|
55
|
-
error: () => onStoreChanged?.(),
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
return () => {
|
|
59
|
-
subscription.unsubscribe()
|
|
60
|
-
cleanup?.()
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const observable = new Observable<TReturn>((observer) => {
|
|
65
|
-
const emitCurrent = () => {
|
|
66
|
-
try {
|
|
67
|
-
observer.next(getCurrent())
|
|
68
|
-
} catch (error) {
|
|
69
|
-
observer.error(error)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
emitCurrent()
|
|
73
|
-
return subscribe(emitCurrent)
|
|
74
|
-
}).pipe(share())
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
getCurrent,
|
|
78
|
-
subscribe,
|
|
79
|
-
observable,
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {createSanityInstance} from '../instance/sanityInstance'
|
|
4
|
-
import {createAction} from './createAction'
|
|
5
|
-
import {createResource, createResourceState} from './createResource'
|
|
6
|
-
import {createStore} from './createStore'
|
|
7
|
-
|
|
8
|
-
describe('createStore', () => {
|
|
9
|
-
const mockInstance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
10
|
-
|
|
11
|
-
interface TestState {
|
|
12
|
-
value: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const testResource = createResource<TestState>({
|
|
16
|
-
name: 'test',
|
|
17
|
-
getInitialState: () => ({value: 0}),
|
|
18
|
-
initialize: vi.fn(),
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
const incrementAction = createAction(testResource, ({state}) => {
|
|
22
|
-
return function () {
|
|
23
|
-
state.set('increment', (prevState) => ({value: prevState.value + 1}))
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
const setAction = createAction(testResource, ({state}) => {
|
|
28
|
-
return function (value: number) {
|
|
29
|
-
state.set('setValue', {value})
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('should return a store with bound actions and a dispose function', () => {
|
|
34
|
-
const store = createStore(testResource, {incrementAction, setAction})
|
|
35
|
-
const instanceStore = store(mockInstance)
|
|
36
|
-
|
|
37
|
-
expect(instanceStore).toHaveProperty('dispose')
|
|
38
|
-
expect(instanceStore).toHaveProperty('incrementAction')
|
|
39
|
-
expect(instanceStore).toHaveProperty('setAction')
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('should bind the actions with state and instance from the sanity instance', () => {
|
|
43
|
-
const store = createStore(testResource, {incrementAction, setAction})
|
|
44
|
-
const instanceStore = store(mockInstance)
|
|
45
|
-
|
|
46
|
-
instanceStore.incrementAction()
|
|
47
|
-
const state = createResourceState<TestState>({value: 0})
|
|
48
|
-
const instanceStore2 = store(mockInstance)
|
|
49
|
-
instanceStore2.setAction(10)
|
|
50
|
-
|
|
51
|
-
expect(state.get()).toEqual({value: 0})
|
|
52
|
-
expect(instanceStore2.setAction).toBeDefined()
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('should dispose of the resource', () => {
|
|
56
|
-
const resource = createResource<TestState>({
|
|
57
|
-
name: 'test',
|
|
58
|
-
getInitialState: () => ({value: 0}),
|
|
59
|
-
initialize: vi.fn(() => vi.fn()),
|
|
60
|
-
})
|
|
61
|
-
const store = createStore(resource, {})
|
|
62
|
-
const instanceStore = store(mockInstance)
|
|
63
|
-
instanceStore.dispose()
|
|
64
|
-
expect(resource.initialize).toHaveBeenCalledTimes(1)
|
|
65
|
-
expect(resource.initialize).toHaveReturnedWith(expect.any(Function))
|
|
66
|
-
})
|
|
67
|
-
})
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import {noop} from 'lodash-es'
|
|
3
|
-
|
|
4
|
-
import {type SanityInstance} from '../instance/types'
|
|
5
|
-
import {type ActionContext, type ResourceAction} from './createAction'
|
|
6
|
-
import {initializeResource, type Resource} from './createResource'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @public
|
|
10
|
-
*/
|
|
11
|
-
export type BoundResourceAction<TParams extends unknown[], TReturn> = (
|
|
12
|
-
...params: TParams
|
|
13
|
-
) => TReturn
|
|
14
|
-
|
|
15
|
-
type BoundActions<TActions extends {[key: string]: ResourceAction<any, any, any>}> = {
|
|
16
|
-
[K in keyof TActions]: TActions[K] extends ResourceAction<any, infer TParams, infer TReturn>
|
|
17
|
-
? BoundResourceAction<TParams, TReturn>
|
|
18
|
-
: never
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type StoreFactory<TActions extends {[key: string]: ResourceAction<any, any, any>}> = (
|
|
22
|
-
instance: SanityInstance | ActionContext<any>,
|
|
23
|
-
) => {
|
|
24
|
-
dispose: () => void
|
|
25
|
-
} & BoundActions<TActions>
|
|
26
|
-
|
|
27
|
-
export function createStore<
|
|
28
|
-
TState,
|
|
29
|
-
TActions extends {[key: string]: ResourceAction<any, any, any>},
|
|
30
|
-
>(resource: Resource<TState>, actions: TActions): StoreFactory<TActions> {
|
|
31
|
-
return function storeFactory(dependencies: SanityInstance | ActionContext<TState>) {
|
|
32
|
-
const instance = 'instance' in dependencies ? dependencies.instance : dependencies
|
|
33
|
-
const {state, dispose} =
|
|
34
|
-
'state' in dependencies
|
|
35
|
-
? {state: dependencies.state, dispose: noop}
|
|
36
|
-
: initializeResource(instance, resource)
|
|
37
|
-
const boundActions = Object.entries(actions).reduce<
|
|
38
|
-
Record<string, BoundResourceAction<unknown[], unknown>>
|
|
39
|
-
>((acc, [key, action]) => {
|
|
40
|
-
acc[key] = action.bind(null, {state, instance})
|
|
41
|
-
return acc
|
|
42
|
-
}, {}) as BoundActions<TActions>
|
|
43
|
-
|
|
44
|
-
return {dispose, ...boundActions}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {type SanityInstance} from '../instance/types'
|
|
4
|
-
import {createStore, type StoreActionContext} from './createStore'
|
|
5
|
-
|
|
6
|
-
// Mock the devtools middleware
|
|
7
|
-
vi.mock('zustand/middleware', () => ({
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
-
devtools: (storeFunction: any) => {
|
|
10
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
-
return (...args: any[]) => storeFunction(...args)
|
|
12
|
-
},
|
|
13
|
-
}))
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
vi.clearAllMocks()
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
describe('createStore', () => {
|
|
20
|
-
// Setup mock instance
|
|
21
|
-
const mockInstance = {
|
|
22
|
-
identity: {
|
|
23
|
-
id: 'test-id',
|
|
24
|
-
},
|
|
25
|
-
} as SanityInstance
|
|
26
|
-
|
|
27
|
-
// Setup types for our test store
|
|
28
|
-
interface TestState {
|
|
29
|
-
count: number
|
|
30
|
-
text: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const initialState: TestState = {
|
|
34
|
-
count: 0,
|
|
35
|
-
text: '',
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Setup test actions
|
|
39
|
-
const createTestActions = () => ({
|
|
40
|
-
increment: ({store}: StoreActionContext<TestState>) => {
|
|
41
|
-
const state = store.getState()
|
|
42
|
-
store.setState({...state, count: state.count + 1})
|
|
43
|
-
},
|
|
44
|
-
setText: ({store}: StoreActionContext<TestState>, newText: string) => {
|
|
45
|
-
const state = store.getState()
|
|
46
|
-
store.setState({...state, text: newText})
|
|
47
|
-
return newText
|
|
48
|
-
},
|
|
49
|
-
getCount: ({store}: StoreActionContext<TestState>) => {
|
|
50
|
-
return store.getState().count
|
|
51
|
-
},
|
|
52
|
-
getText: ({store}: StoreActionContext<TestState>) => {
|
|
53
|
-
return store.getState().text
|
|
54
|
-
},
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('creates a store with initial state with all properties', () => {
|
|
58
|
-
const actions = createTestActions()
|
|
59
|
-
const store = createStore(initialState, actions, {
|
|
60
|
-
name: 'test-store',
|
|
61
|
-
instance: mockInstance,
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
// Verify store has all action methods
|
|
65
|
-
expect(store).toHaveProperty('increment')
|
|
66
|
-
expect(store).toHaveProperty('setText')
|
|
67
|
-
expect(store).toHaveProperty('getCount')
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('maintains separate state for different stores', () => {
|
|
71
|
-
const actions = createTestActions()
|
|
72
|
-
const store1 = createStore(initialState, actions, {
|
|
73
|
-
name: 'store-1',
|
|
74
|
-
instance: mockInstance,
|
|
75
|
-
})
|
|
76
|
-
const store2 = createStore(initialState, actions, {
|
|
77
|
-
name: 'store-2',
|
|
78
|
-
instance: mockInstance,
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
store1.increment()
|
|
82
|
-
store2.setText('hello')
|
|
83
|
-
|
|
84
|
-
// Verify stores are unaffected
|
|
85
|
-
expect(store1.getCount()).toBe(1)
|
|
86
|
-
expect(store2.getCount()).toBe(0)
|
|
87
|
-
|
|
88
|
-
expect(store1.getText()).toBe('')
|
|
89
|
-
expect(store2.getText()).toBe('hello')
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('correctly updates state through actions', () => {
|
|
93
|
-
const actions = createTestActions()
|
|
94
|
-
const store = createStore(initialState, actions, {
|
|
95
|
-
name: 'test-store',
|
|
96
|
-
instance: mockInstance,
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
store.increment()
|
|
100
|
-
expect(store.getCount()).toBe(1)
|
|
101
|
-
|
|
102
|
-
store.increment()
|
|
103
|
-
expect(store.getCount()).toBe(2)
|
|
104
|
-
|
|
105
|
-
store.setText('hello world')
|
|
106
|
-
expect(store.getText()).toBe('hello world')
|
|
107
|
-
})
|
|
108
|
-
})
|
package/src/store/createStore.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import {devtools} from 'zustand/middleware'
|
|
3
|
-
import {createStore as createZustandStore, type StoreApi} from 'zustand/vanilla'
|
|
4
|
-
|
|
5
|
-
import {type SanityInstance} from '../instance/types'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Creates a (vanilla) zustand store with the given initial state and actions.
|
|
9
|
-
* Only the actions will be returned - actions should be created to interact with it.
|
|
10
|
-
*
|
|
11
|
-
* The rationale for this is to encapsulate the internal state so only the store knows about it,
|
|
12
|
-
* making refactoring easier and more predictable. While this seems like good practice, I am unsure
|
|
13
|
-
* if it might get in our way once we start wanting to subscribe to finer piece of state or similar.
|
|
14
|
-
* I will leave it for now, and we can see if it becomes a problem.
|
|
15
|
-
*
|
|
16
|
-
* @param initialState - Initial state of the store
|
|
17
|
-
* @param actions - The actions available on the store
|
|
18
|
-
* @param options - Options for the store
|
|
19
|
-
* @returns The actions available on the store
|
|
20
|
-
* @internal
|
|
21
|
-
*/
|
|
22
|
-
export function createStore<S, A extends StoreActionMap<S>>(
|
|
23
|
-
initialState: S,
|
|
24
|
-
actions: A,
|
|
25
|
-
{name, instance}: StoreOptions,
|
|
26
|
-
): CurriedActions<S, A> {
|
|
27
|
-
const uniqueName = `${name}-${instance.identity.id}`
|
|
28
|
-
const store = createZustandStore<S>()(devtools(() => ({...initialState}), {name: uniqueName}))
|
|
29
|
-
const context = {store, instance}
|
|
30
|
-
return createCurriedActions(actions, context)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Context passed to store actions as first argument.
|
|
35
|
-
*
|
|
36
|
-
* @internal
|
|
37
|
-
*/
|
|
38
|
-
export interface StoreActionContext<S> {
|
|
39
|
-
/**
|
|
40
|
-
* The store API, from zustand. Contains `getState()`, `setState()`, and `subscribe()`.
|
|
41
|
-
*/
|
|
42
|
-
store: StoreApi<S>
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* The Sanity SDK instance associated with the store. Often used
|
|
46
|
-
*/
|
|
47
|
-
instance: SanityInstance
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* An action that can be performed on a store.
|
|
52
|
-
*
|
|
53
|
-
* @param context - Store context. Contains the store API (`getState()`, `setState()`, `subscribe()`), as well as the SDK instance associated with it.
|
|
54
|
-
* @internal
|
|
55
|
-
*/
|
|
56
|
-
type StoreAction<S> = (context: StoreActionContext<S>, ...args: any[]) => any
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* A map of actions that can be performed on a store.
|
|
60
|
-
*
|
|
61
|
-
* @internal
|
|
62
|
-
*/
|
|
63
|
-
type StoreActionMap<S> = Record<string, StoreAction<S>>
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Options for creating a store.
|
|
67
|
-
*
|
|
68
|
-
* @internal
|
|
69
|
-
*/
|
|
70
|
-
interface StoreOptions {
|
|
71
|
-
/**
|
|
72
|
-
* Name used for debugging
|
|
73
|
-
*/
|
|
74
|
-
name: string
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* The Sanity SDK instance associated with the store
|
|
78
|
-
*/
|
|
79
|
-
instance: SanityInstance
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function createCurriedActions<S, A extends StoreActionMap<S>>(
|
|
83
|
-
actions: A,
|
|
84
|
-
store: StoreActionContext<S>,
|
|
85
|
-
): CurriedActions<S, A> {
|
|
86
|
-
const curried: CurriedActions<S, A> = {} as CurriedActions<S, A>
|
|
87
|
-
return Object.entries(actions).reduce((acc, [key, action]) => {
|
|
88
|
-
const curriedAction = (...args: RestParameters<typeof action>) => action(store, ...args)
|
|
89
|
-
return {...acc, [key]: curriedAction}
|
|
90
|
-
}, curried)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
type RestParameters<T extends (...args: any[]) => any> = T extends (
|
|
94
|
-
first: any,
|
|
95
|
-
...rest: infer R
|
|
96
|
-
) => any
|
|
97
|
-
? R
|
|
98
|
-
: never
|
|
99
|
-
|
|
100
|
-
type ActionReturn<T extends (...args: any[]) => any> = ReturnType<T>
|
|
101
|
-
|
|
102
|
-
type CurriedActions<S, A extends StoreActionMap<S>> = {
|
|
103
|
-
[K in keyof A]: A[K] extends StoreAction<S>
|
|
104
|
-
? (...args: RestParameters<A[K]>) => ActionReturn<A[K]>
|
|
105
|
-
: never
|
|
106
|
-
}
|
|
File without changes
|