@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.
Files changed (127) hide show
  1. package/dist/index.d.ts +428 -325
  2. package/dist/index.js +1618 -1553
  3. package/dist/index.js.map +1 -1
  4. package/package.json +6 -7
  5. package/src/_exports/index.ts +31 -30
  6. package/src/auth/authStore.test.ts +149 -104
  7. package/src/auth/authStore.ts +51 -100
  8. package/src/auth/handleAuthCallback.test.ts +67 -34
  9. package/src/auth/handleAuthCallback.ts +8 -7
  10. package/src/auth/logout.test.ts +61 -29
  11. package/src/auth/logout.ts +26 -28
  12. package/src/auth/refreshStampedToken.test.ts +9 -9
  13. package/src/auth/refreshStampedToken.ts +62 -56
  14. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +5 -5
  15. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +45 -47
  16. package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -5
  17. package/src/auth/subscribeToStorageEventsAndSetToken.ts +22 -24
  18. package/src/client/clientStore.test.ts +131 -67
  19. package/src/client/clientStore.ts +117 -116
  20. package/src/comlink/controller/actions/destroyController.test.ts +38 -13
  21. package/src/comlink/controller/actions/destroyController.ts +11 -15
  22. package/src/comlink/controller/actions/getOrCreateChannel.test.ts +56 -27
  23. package/src/comlink/controller/actions/getOrCreateChannel.ts +37 -35
  24. package/src/comlink/controller/actions/getOrCreateController.test.ts +27 -16
  25. package/src/comlink/controller/actions/getOrCreateController.ts +23 -22
  26. package/src/comlink/controller/actions/releaseChannel.test.ts +37 -13
  27. package/src/comlink/controller/actions/releaseChannel.ts +22 -21
  28. package/src/comlink/controller/comlinkControllerStore.test.ts +65 -36
  29. package/src/comlink/controller/comlinkControllerStore.ts +44 -5
  30. package/src/comlink/node/actions/getOrCreateNode.test.ts +31 -15
  31. package/src/comlink/node/actions/getOrCreateNode.ts +30 -29
  32. package/src/comlink/node/actions/releaseNode.test.ts +75 -55
  33. package/src/comlink/node/actions/releaseNode.ts +19 -21
  34. package/src/comlink/node/comlinkNodeStore.test.ts +6 -11
  35. package/src/comlink/node/comlinkNodeStore.ts +22 -5
  36. package/src/config/authConfig.ts +79 -0
  37. package/src/config/sanityConfig.ts +48 -0
  38. package/src/datasets/datasets.test.ts +2 -2
  39. package/src/datasets/datasets.ts +18 -5
  40. package/src/document/actions.test.ts +22 -10
  41. package/src/document/actions.ts +44 -56
  42. package/src/document/applyDocumentActions.test.ts +96 -36
  43. package/src/document/applyDocumentActions.ts +140 -99
  44. package/src/document/documentStore.test.ts +103 -155
  45. package/src/document/documentStore.ts +247 -237
  46. package/src/document/listen.ts +56 -55
  47. package/src/document/patchOperations.ts +0 -43
  48. package/src/document/permissions.test.ts +25 -12
  49. package/src/document/permissions.ts +11 -4
  50. package/src/document/processActions.test.ts +41 -8
  51. package/src/document/reducers.test.ts +87 -16
  52. package/src/document/reducers.ts +2 -2
  53. package/src/document/sharedListener.test.ts +34 -16
  54. package/src/document/sharedListener.ts +33 -11
  55. package/src/preview/getPreviewState.test.ts +40 -39
  56. package/src/preview/getPreviewState.ts +68 -56
  57. package/src/preview/previewConstants.ts +43 -0
  58. package/src/preview/previewQuery.test.ts +1 -1
  59. package/src/preview/previewQuery.ts +4 -5
  60. package/src/preview/previewStore.test.ts +13 -58
  61. package/src/preview/previewStore.ts +7 -21
  62. package/src/preview/resolvePreview.test.ts +33 -104
  63. package/src/preview/resolvePreview.ts +11 -21
  64. package/src/preview/subscribeToStateAndFetchBatches.test.ts +96 -97
  65. package/src/preview/subscribeToStateAndFetchBatches.ts +85 -81
  66. package/src/preview/util.ts +1 -0
  67. package/src/project/project.test.ts +3 -3
  68. package/src/project/project.ts +28 -5
  69. package/src/projection/getProjectionState.test.ts +69 -49
  70. package/src/projection/getProjectionState.ts +42 -50
  71. package/src/projection/projectionQuery.ts +1 -1
  72. package/src/projection/projectionStore.test.ts +13 -51
  73. package/src/projection/projectionStore.ts +6 -18
  74. package/src/projection/resolveProjection.test.ts +32 -127
  75. package/src/projection/resolveProjection.ts +15 -28
  76. package/src/projection/subscribeToStateAndFetchBatches.test.ts +105 -90
  77. package/src/projection/subscribeToStateAndFetchBatches.ts +94 -81
  78. package/src/projection/util.ts +2 -0
  79. package/src/projects/projects.test.ts +13 -4
  80. package/src/projects/projects.ts +6 -1
  81. package/src/query/queryStore.test.ts +10 -47
  82. package/src/query/queryStore.ts +151 -133
  83. package/src/query/queryStoreConstants.ts +2 -0
  84. package/src/store/createActionBinder.test.ts +153 -0
  85. package/src/store/createActionBinder.ts +176 -0
  86. package/src/store/createSanityInstance.test.ts +84 -0
  87. package/src/store/createSanityInstance.ts +124 -0
  88. package/src/store/createStateSourceAction.test.ts +196 -0
  89. package/src/store/createStateSourceAction.ts +260 -0
  90. package/src/store/createStoreInstance.test.ts +81 -0
  91. package/src/store/createStoreInstance.ts +80 -0
  92. package/src/store/createStoreState.test.ts +85 -0
  93. package/src/store/createStoreState.ts +92 -0
  94. package/src/store/defineStore.test.ts +18 -0
  95. package/src/store/defineStore.ts +81 -0
  96. package/src/users/reducers.test.ts +318 -0
  97. package/src/users/reducers.ts +88 -0
  98. package/src/users/types.ts +46 -4
  99. package/src/users/usersConstants.ts +4 -0
  100. package/src/users/usersStore.test.ts +350 -223
  101. package/src/users/usersStore.ts +285 -149
  102. package/src/utils/createFetcherStore.test.ts +6 -7
  103. package/src/utils/createFetcherStore.ts +150 -153
  104. package/src/{common/util.test.ts → utils/hashString.test.ts} +1 -1
  105. package/src/auth/fetchLoginUrls.test.ts +0 -163
  106. package/src/auth/fetchLoginUrls.ts +0 -74
  107. package/src/common/createLiveEventSubscriber.test.ts +0 -121
  108. package/src/common/createLiveEventSubscriber.ts +0 -55
  109. package/src/common/types.ts +0 -4
  110. package/src/instance/identity.test.ts +0 -46
  111. package/src/instance/identity.ts +0 -29
  112. package/src/instance/sanityInstance.test.ts +0 -77
  113. package/src/instance/sanityInstance.ts +0 -57
  114. package/src/instance/types.ts +0 -37
  115. package/src/preview/getPreviewProjection.ts +0 -45
  116. package/src/resources/README.md +0 -370
  117. package/src/resources/createAction.test.ts +0 -101
  118. package/src/resources/createAction.ts +0 -44
  119. package/src/resources/createResource.test.ts +0 -112
  120. package/src/resources/createResource.ts +0 -102
  121. package/src/resources/createStateSourceAction.test.ts +0 -114
  122. package/src/resources/createStateSourceAction.ts +0 -83
  123. package/src/resources/createStore.test.ts +0 -67
  124. package/src/resources/createStore.ts +0 -46
  125. package/src/store/createStore.test.ts +0 -108
  126. package/src/store/createStore.ts +0 -106
  127. /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
- })
@@ -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