@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
package/src/resources/README.md
DELETED
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
# SDK Resource Management
|
|
2
|
-
|
|
3
|
-
## Resources
|
|
4
|
-
|
|
5
|
-
In the SDK, we group together state and functions that concern that state into a unit called a **Resource**.
|
|
6
|
-
|
|
7
|
-
To create a resource, call `createResource` with a `name` for the resource (this will be used as the key in the resource cache), a `getInitialState` function which takes in a `SanityInstance` and return the initial state, and an optional initialize function that can set up any subscriptions.
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
// documentList.ts
|
|
11
|
-
import {SyncTag} from '@sanity/client'
|
|
12
|
-
import {createResource} from '../resources/createResource'
|
|
13
|
-
import {subscribeToLiveContentAndSetLastLiveEventId} from './subscribeToLiveContentAndSetLastLiveEventId'
|
|
14
|
-
|
|
15
|
-
// declare the state shape for this resource
|
|
16
|
-
interface DocumentListState {
|
|
17
|
-
results: DocumentHandle[]
|
|
18
|
-
syncTags: SyncTag[]
|
|
19
|
-
lastLiveEventId: string | null
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const documentList = createResource<DocumentListState>({
|
|
23
|
-
name: 'documentList',
|
|
24
|
-
getInitialState(instance) {
|
|
25
|
-
// utilize the instance to set a different initial state
|
|
26
|
-
// instance.config.someConfigOption
|
|
27
|
-
|
|
28
|
-
return {results: [], syncTags: [], lastLiveEventId: null}
|
|
29
|
-
},
|
|
30
|
-
initialize() {
|
|
31
|
-
// set up subscriptions
|
|
32
|
-
const liveContentSubscription = subscribeToLiveContentAndSetLastLiveEventId(this)
|
|
33
|
-
|
|
34
|
-
return () => {
|
|
35
|
-
// teardown / cleanup
|
|
36
|
-
// this function will be ran when `instance.dispose()` is called
|
|
37
|
-
liveContentSubscription.unsubscribe()
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
})
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Actions
|
|
44
|
-
|
|
45
|
-
**Actions** are functions that are auto-magically bound to a resource by using `createAction`.
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
// subscribeToLiveContentAndSetLastLiveEventId.ts
|
|
49
|
-
import {createAction} from '../resources/createAction'
|
|
50
|
-
import {getClientSource} from '../client/getClientSource'
|
|
51
|
-
import {documentList} from './documentList'
|
|
52
|
-
|
|
53
|
-
// use `createAction` to create a function that binds the resource's state
|
|
54
|
-
export const subscribeToLiveContentAndSetLastLiveEventId = createAction(
|
|
55
|
-
// provide the resource you'd like to bind this action to
|
|
56
|
-
() => documentList,
|
|
57
|
-
// provide the implementation of the function using the provided `state` or `instance`
|
|
58
|
-
({state, instance}) => {
|
|
59
|
-
// return a function, the parameters here will be the parameters of the resulting action
|
|
60
|
-
return function () {
|
|
61
|
-
const client$ = getClientSource(instance).observable
|
|
62
|
-
const liveMessage$ = client$.pipe(switchMap((client) => client.live.events()))
|
|
63
|
-
|
|
64
|
-
return liveMessage$.subscribe((e) => {
|
|
65
|
-
const {syncTags} = state.get()
|
|
66
|
-
if (e.type === 'message' && e.tags.some((tag) => syncTags.includes(tag))) {
|
|
67
|
-
state.set('setLastLiveEventId', {lastLiveEventId: e.id})
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
)
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
`createAction` returns a function that takes in either just an `instance` or an object with a instance and the initialized resource state `{instance, state}`.
|
|
76
|
-
|
|
77
|
-
If the initialized state is not passed into the action, it will call `getOrCreateResource` to grab the existing resource state or to initialize the resource state if not already created. Take a look at the [implementation](./createAction.ts) to see this in action.
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
When using a resource action from the same resource, the convention is to pass `this` which is bound to an object that contains the initialized state and the current instance.
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
import {createAction} from '../resources/createAction'
|
|
85
|
-
import {fooResource} from './fooResource'
|
|
86
|
-
|
|
87
|
-
const privateFooAction = createAction(
|
|
88
|
-
() => fooResource,
|
|
89
|
-
({state, instance}) => {
|
|
90
|
-
return function (value: string) {
|
|
91
|
-
// do something with state
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
export const publicFooAction = createAction(
|
|
97
|
-
() => fooResource,
|
|
98
|
-
({state, instance}) => {
|
|
99
|
-
return function () {
|
|
100
|
-
const result = privateFooAction(this, 'some value')
|
|
101
|
-
return result
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
)
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
When using a resource action from a different resource, the convention is to just pass in an instance and rely on `getOrCreateResource` to pull or create the state for the resource.
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
import {createAction} from '../resources/createAction'
|
|
111
|
-
import {barResource} from './barResource'
|
|
112
|
-
import {publicFooAction} from '../foo/publicFooAction'
|
|
113
|
-
|
|
114
|
-
export const barActionThatUsesFooAction = createAction(
|
|
115
|
-
() => barResource,
|
|
116
|
-
({state, instance}) => {
|
|
117
|
-
return function () {
|
|
118
|
-
const result = publicFooAction(instance)
|
|
119
|
-
return `from foo: ${result}`
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
)
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
`this` is also available to be used in the `initialize` function provided to `createResource`
|
|
126
|
-
|
|
127
|
-
```ts
|
|
128
|
-
import {createResource} from '../resources/createResource'
|
|
129
|
-
import {barActionThatUsesFooAction} from './barActionThatUsesFooAction'
|
|
130
|
-
|
|
131
|
-
const barResource = createResource({
|
|
132
|
-
name: 'bar',
|
|
133
|
-
getInitialState: () => ({}),
|
|
134
|
-
initialize() {
|
|
135
|
-
const result = barActionThatUsesFooAction(this)
|
|
136
|
-
// ...
|
|
137
|
-
|
|
138
|
-
return () => {
|
|
139
|
-
// ...
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
})
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## Stores
|
|
146
|
-
|
|
147
|
-
By default, actions are global meaning that calling them with a sanity instance will result in global resource state available to all actions that use the same resource.
|
|
148
|
-
|
|
149
|
-
There are certain cases like the document list where we do not want global state. This is where `createStore` comes in.
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
interface TestState {
|
|
153
|
-
value: number
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const testResource = createResource<TestState>({
|
|
157
|
-
name: 'test',
|
|
158
|
-
getInitialState: () => ({value: 0}),
|
|
159
|
-
initialize() {
|
|
160
|
-
// set up subscriptions etc
|
|
161
|
-
return () => {}
|
|
162
|
-
},
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
const inc = createAction(
|
|
166
|
-
() => testResource,
|
|
167
|
-
({state}) => {
|
|
168
|
-
return function () {
|
|
169
|
-
state.set('increment', (prevState) => ({value: prevState.value + 1}))
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
const set = createAction(
|
|
175
|
-
() => testResource,
|
|
176
|
-
({state}) => {
|
|
177
|
-
return function (value: number) {
|
|
178
|
-
state.set('setValue', {value})
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
const get = createAction(
|
|
184
|
-
() => testResource,
|
|
185
|
-
({state}) => {
|
|
186
|
-
return function () {
|
|
187
|
-
return state.get().value
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
export const createTestStore = createStore(testResource, {inc, set, get})
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
Now `createTestStore` will return an object with the actions bound to it as methods.
|
|
196
|
-
|
|
197
|
-
```ts
|
|
198
|
-
import {createTestStore} from './createTestStore'
|
|
199
|
-
|
|
200
|
-
const testStore = createTestStore(instance)
|
|
201
|
-
|
|
202
|
-
console.log(testStore.get()) // 0
|
|
203
|
-
testStore.inc()
|
|
204
|
-
|
|
205
|
-
console.log(testStore.get()) // 1
|
|
206
|
-
|
|
207
|
-
// calls the clean up function returned in initialize
|
|
208
|
-
testStore.dispose()
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
## Resource State
|
|
212
|
-
|
|
213
|
-
All resource actions are provided with a `state` param of type `ResourceState` that contains a `get`, `set`, and `observable` properties.
|
|
214
|
-
|
|
215
|
-
```ts
|
|
216
|
-
export type ResourceState<TState> = {
|
|
217
|
-
get: () => TState
|
|
218
|
-
set: (name: string, state: Partial<TState> | ((s: TState) => Partial<TState>)) => void
|
|
219
|
-
observable: Observable<TState>
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
```ts
|
|
224
|
-
import {map, distinctUntilChanged} from 'rxjs'
|
|
225
|
-
import {createAction} from '../resources/createAction'
|
|
226
|
-
import {myResource} from './myResource'
|
|
227
|
-
|
|
228
|
-
export const myAction = createAction(
|
|
229
|
-
() => myResource,
|
|
230
|
-
({state, instance}) => {
|
|
231
|
-
return function (value: string) {
|
|
232
|
-
// call `state.get` to get the current resource state
|
|
233
|
-
const currentState = state.get()
|
|
234
|
-
|
|
235
|
-
// call `state.set` with an update label (for redux dev tools)
|
|
236
|
-
// with either a new state value or a function that returns new state.
|
|
237
|
-
// Note: state.set is powered by zustand which always shallowly
|
|
238
|
-
// with the previous state in the store
|
|
239
|
-
// https://zustand.docs.pmnd.rs/guides/updating-state
|
|
240
|
-
state.set('stateUpdateLabel', (prev) => ({foo: value}))
|
|
241
|
-
|
|
242
|
-
// you can also subscribe to internal state changes via `state.observable`
|
|
243
|
-
const subscription = state.observable
|
|
244
|
-
.pipe(
|
|
245
|
-
map((s) => s.foo),
|
|
246
|
-
distinctUntilChanged(),
|
|
247
|
-
)
|
|
248
|
-
.subscribe(console.log)
|
|
249
|
-
|
|
250
|
-
return subscription
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
)
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
## State Sources
|
|
257
|
-
|
|
258
|
-
A `StateSource` represents a stream of changing values derived from the state of a resource.
|
|
259
|
-
|
|
260
|
-
```ts
|
|
261
|
-
export interface StateSource<T> {
|
|
262
|
-
// `getCurrent` allows the current state to be pulled without creating a subscription
|
|
263
|
-
getCurrent: () => T
|
|
264
|
-
// `subscribe` allows us to notify consumers when a new value is available.
|
|
265
|
-
// note that this callback does not accept any parameters and the expectation
|
|
266
|
-
// is for consumers to pull from `getCurrent`.
|
|
267
|
-
subscribe: (onStoreChanged: () => void) => () => void
|
|
268
|
-
// `observable` is provides combination of the above. on subscribe, this will
|
|
269
|
-
// emit the current value from `getCurrent` and will update when the above
|
|
270
|
-
// `subscribe` fires
|
|
271
|
-
observable: Observable<T>
|
|
272
|
-
}
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
By convention, actions that return state sources should end with the `-State` suffix.
|
|
276
|
-
|
|
277
|
-
```ts
|
|
278
|
-
// example usage in a hook
|
|
279
|
-
import {getSchemaState} from '@sanity/sdk'
|
|
280
|
-
import {useSanityInstance} from '@sanity/sdk-react'
|
|
281
|
-
|
|
282
|
-
export function useSchema(): Schema {
|
|
283
|
-
const instance = useSanityInstance()
|
|
284
|
-
const {subscribe, getCurrent} = useMemo(() => getSchemaState(instance), [instance])
|
|
285
|
-
|
|
286
|
-
return useSyncExternalStore(subscribe, getCurrent)
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
```ts
|
|
291
|
-
// example usage in an action
|
|
292
|
-
import {getSchemaState} from '../schema/getSchemaState'
|
|
293
|
-
import {createAction} from '../resources/createAction'
|
|
294
|
-
import {myResource} from './myResource'
|
|
295
|
-
|
|
296
|
-
export const myAction = createAction(
|
|
297
|
-
() => myResource,
|
|
298
|
-
({state, instance}) => {
|
|
299
|
-
return function () {
|
|
300
|
-
const schema$ = getSchemaState(instance).observable
|
|
301
|
-
|
|
302
|
-
// ...
|
|
303
|
-
}
|
|
304
|
-
},
|
|
305
|
-
)
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
Creating a state source involves creating a **selector** to pull the desired value from state and then using `createStateSourceAction`.
|
|
309
|
-
|
|
310
|
-
```ts
|
|
311
|
-
import {schemaStore} from './schemaStore'
|
|
312
|
-
import {createStateSourceAction} from '../resources/createStateSourceAction'
|
|
313
|
-
|
|
314
|
-
export const getSchemaState = createStateSourceAction(
|
|
315
|
-
() => schemaStore, // pick the resource
|
|
316
|
-
(state) => state.schema, // write a selector
|
|
317
|
-
)
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
## Selectors
|
|
321
|
-
|
|
322
|
-
Selectors are functions that extract and compute derived data from the resource state. When used with `createStateSourceAction`, they help create efficient state sources that only update when their selected data changes.
|
|
323
|
-
|
|
324
|
-
```ts
|
|
325
|
-
import {createSelector} from 'reselect'
|
|
326
|
-
import {createStateSourceAction} from '../resources/createStateSourceAction'
|
|
327
|
-
|
|
328
|
-
interface DocumentListState {
|
|
329
|
-
options: DocumentListOptions
|
|
330
|
-
lastLiveEventId?: string
|
|
331
|
-
syncTags: SyncTag[]
|
|
332
|
-
limit: number
|
|
333
|
-
results: DocumentHandle[]
|
|
334
|
-
isPending: boolean
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const documentList = createResource<DocumentListState>({
|
|
338
|
-
name: 'documentList',
|
|
339
|
-
// ... resource implementation
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
const getDocumentListState = createStateSourceAction(
|
|
343
|
-
() => documentList,
|
|
344
|
-
// Basic selector that returns multiple state properties
|
|
345
|
-
createSelector(
|
|
346
|
-
[(state: DocumentListState) => state.results, (state: DocumentListState) => state.isPending],
|
|
347
|
-
(results, isPending) => ({results, isPending}),
|
|
348
|
-
),
|
|
349
|
-
)
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
**When to Use Reselect**
|
|
353
|
-
|
|
354
|
-
If your selector simply returns an existing object from state without creating a new derived value, you **don't need reselect**:
|
|
355
|
-
|
|
356
|
-
```ts
|
|
357
|
-
// Simple selector that just returns an existing object - reselect not needed
|
|
358
|
-
const getResults = createStateSourceAction(
|
|
359
|
-
() => documentList,
|
|
360
|
-
(state: DocumentListState) => state.results,
|
|
361
|
-
)
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
However, when computing derived data that creates new objects, arrays, or values, `reselect` provides several key benefits:
|
|
365
|
-
|
|
366
|
-
1. **Memoization**: The `createSelector` utility from reselect caches results and only recomputes when input selectors return new values, reducing unnecessary calculations.
|
|
367
|
-
2. **Reference Stability**: By memoizing results, reselect helps maintain stable references which is crucial for preventing infinite loops in state source subscriptions.
|
|
368
|
-
3. **Composition**: Input selectors can be either plain functions or other selectors, allowing you to build complex derivations while maintaining performance.
|
|
369
|
-
|
|
370
|
-
Read more at [reselect.js.org](https://reselect.js.org/)
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import {describe, expect, it} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {createSanityInstance} from '../instance/sanityInstance'
|
|
4
|
-
import {type ActionContext, createAction, createInternalAction} from './createAction'
|
|
5
|
-
import {createResource, createResourceState} from './createResource'
|
|
6
|
-
|
|
7
|
-
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
8
|
-
|
|
9
|
-
interface TestState {
|
|
10
|
-
value: number
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const testResource = createResource<TestState>({
|
|
14
|
-
name: 'test',
|
|
15
|
-
getInitialState: () => ({value: 0}),
|
|
16
|
-
initialize() {
|
|
17
|
-
return () => {}
|
|
18
|
-
},
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
describe('createAction', () => {
|
|
22
|
-
it('should create an action that can access state and instance', () => {
|
|
23
|
-
// Define an action that accesses state and instance
|
|
24
|
-
const testAction = createAction(testResource, ({state, instance: {identity}}) => {
|
|
25
|
-
return function () {
|
|
26
|
-
state.set('increment', (prev) => ({value: prev.value + 1}))
|
|
27
|
-
return identity.projectId
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
// Call the action with instance
|
|
32
|
-
const result = testAction(instance)
|
|
33
|
-
|
|
34
|
-
// Verify that the state and instance are correctly passed into the action
|
|
35
|
-
expect(result).toBe('test')
|
|
36
|
-
expect(createResourceState({value: 0}).get()).toEqual({value: 0})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('should correctly update the state using set', () => {
|
|
40
|
-
const testAction = createAction(testResource, ({state, instance: {identity}}) => {
|
|
41
|
-
return function () {
|
|
42
|
-
state.set('increment', (prev) => ({value: prev.value + 1}))
|
|
43
|
-
return identity.projectId
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const state = createResourceState({value: 1})
|
|
48
|
-
const actionContext: ActionContext<TestState> = {instance, state}
|
|
49
|
-
|
|
50
|
-
// call the action with state
|
|
51
|
-
const projectId = testAction(actionContext)
|
|
52
|
-
expect(projectId).toBe(instance.identity.projectId)
|
|
53
|
-
|
|
54
|
-
// Verify that the state has been changed
|
|
55
|
-
expect(state.get()).toEqual({value: 2})
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('should bind the action to a provided context and work without instance', () => {
|
|
59
|
-
const testAction = createAction(testResource, ({state}) => {
|
|
60
|
-
return function (value: number) {
|
|
61
|
-
state.set('updateValue', {value: value})
|
|
62
|
-
return state.get().value
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
const mockState = createResourceState({value: 10})
|
|
67
|
-
const result = testAction(
|
|
68
|
-
{state: mockState, instance: instance} as ActionContext<TestState>,
|
|
69
|
-
20,
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
expect(result).toBe(20)
|
|
73
|
-
expect(mockState.get()).toEqual({value: 20})
|
|
74
|
-
|
|
75
|
-
const mockState2 = createResourceState({value: 10})
|
|
76
|
-
const result2 = testAction({state: mockState2} as ActionContext<TestState>, 30)
|
|
77
|
-
expect(result2).toBe(30)
|
|
78
|
-
expect(mockState2.get()).toEqual({value: 30})
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
describe('createInternalAction', () => {
|
|
83
|
-
it('creates an action that requires state and instance', () => {
|
|
84
|
-
// Define an action that accesses state and instance
|
|
85
|
-
const testAction = createInternalAction<TestState, [], string>(
|
|
86
|
-
({state, instance: {identity}}) => {
|
|
87
|
-
return function () {
|
|
88
|
-
state.set('increment', (prev) => ({value: prev.value + 1}))
|
|
89
|
-
return identity.projectId
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
const result = testAction({
|
|
95
|
-
state: createResourceState({value: 10}),
|
|
96
|
-
instance: instance,
|
|
97
|
-
} as ActionContext<TestState>)
|
|
98
|
-
|
|
99
|
-
expect(result).toBe('test')
|
|
100
|
-
})
|
|
101
|
-
})
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import {type SanityInstance} from '../instance/types'
|
|
2
|
-
import {getOrCreateResource, type Resource, type ResourceState} from './createResource'
|
|
3
|
-
|
|
4
|
-
/** @public */
|
|
5
|
-
export interface ActionContext<TState> {
|
|
6
|
-
instance: SanityInstance
|
|
7
|
-
state: ResourceState<TState>
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
type ResourceActionDefinition<TState, TParams extends unknown[], TReturn> = (
|
|
11
|
-
options: ActionContext<TState>,
|
|
12
|
-
) => (this: ActionContext<TState>, ...args: TParams) => TReturn
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @public
|
|
16
|
-
*/
|
|
17
|
-
export type ResourceAction<TState, TParams extends unknown[], TReturn> = (
|
|
18
|
-
dependencies: SanityInstance | ActionContext<TState>,
|
|
19
|
-
...params: TParams
|
|
20
|
-
) => TReturn
|
|
21
|
-
|
|
22
|
-
export function createAction<TState, TParams extends unknown[], TReturn>(
|
|
23
|
-
resource: Resource<TState>,
|
|
24
|
-
actionDefinition: ResourceActionDefinition<TState, TParams, TReturn>,
|
|
25
|
-
): ResourceAction<TState, TParams, TReturn> {
|
|
26
|
-
return (dependencies: SanityInstance | ActionContext<TState>, ...args: TParams): TReturn => {
|
|
27
|
-
const instance = 'state' in dependencies ? dependencies.instance : dependencies
|
|
28
|
-
const {state} =
|
|
29
|
-
'state' in dependencies ? dependencies : getOrCreateResource(dependencies, resource)
|
|
30
|
-
const actionContext = {instance, state}
|
|
31
|
-
return actionDefinition(actionContext).bind(actionContext)(...args)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @internal
|
|
37
|
-
*/
|
|
38
|
-
export function createInternalAction<TState, TParams extends unknown[], TReturn>(
|
|
39
|
-
actionDefinition: ResourceActionDefinition<TState, TParams, TReturn>,
|
|
40
|
-
) {
|
|
41
|
-
return (actionContext: ActionContext<TState>, ...args: TParams): TReturn => {
|
|
42
|
-
return actionDefinition(actionContext).bind(actionContext)(...args)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {createSanityInstance} from '../instance/sanityInstance'
|
|
4
|
-
import {
|
|
5
|
-
createResource,
|
|
6
|
-
disposeResources,
|
|
7
|
-
getOrCreateResource,
|
|
8
|
-
initializeResource,
|
|
9
|
-
type Resource,
|
|
10
|
-
} from './createResource'
|
|
11
|
-
|
|
12
|
-
describe('createResource', () => {
|
|
13
|
-
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
14
|
-
|
|
15
|
-
interface TestState {
|
|
16
|
-
value: number
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const testResource: Resource<TestState> = createResource<TestState>({
|
|
20
|
-
name: 'test',
|
|
21
|
-
getInitialState: () => ({value: 0}),
|
|
22
|
-
initialize: vi.fn(),
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('should return the same state from getOrCreateResource', () => {
|
|
26
|
-
const resource1 = getOrCreateResource(instance, testResource)
|
|
27
|
-
const resource2 = getOrCreateResource(instance, testResource)
|
|
28
|
-
expect(resource1).toBe(resource2)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('should initialize a resource with initial state', () => {
|
|
32
|
-
const initialState = {value: 1}
|
|
33
|
-
const resource = createResource<TestState>({
|
|
34
|
-
name: 'test',
|
|
35
|
-
getInitialState: () => initialState,
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
const {state} = initializeResource(instance, resource)
|
|
39
|
-
expect(state.get()).toEqual(initialState)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('should invoke the init function on initialization', () => {
|
|
43
|
-
const resource = createResource<TestState>({
|
|
44
|
-
name: 'test',
|
|
45
|
-
getInitialState: () => ({value: 0}),
|
|
46
|
-
initialize: vi.fn(),
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
initializeResource(instance, resource)
|
|
50
|
-
expect(resource.initialize).toHaveBeenCalledTimes(1)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('should update the state using set method', () => {
|
|
54
|
-
const resource = createResource<TestState>({
|
|
55
|
-
name: 'test',
|
|
56
|
-
getInitialState: () => ({value: 0}),
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
const {state} = initializeResource(instance, resource)
|
|
60
|
-
state.set('increment', (prevState) => ({value: prevState.value + 1}))
|
|
61
|
-
expect(state.get()).toEqual({value: 1})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('should create an observable from the state', () => {
|
|
65
|
-
const resource = createResource<TestState>({
|
|
66
|
-
name: 'test',
|
|
67
|
-
getInitialState: () => ({value: 0}),
|
|
68
|
-
})
|
|
69
|
-
const {state} = initializeResource(instance, resource)
|
|
70
|
-
const next = vi.fn()
|
|
71
|
-
state.observable.subscribe(next)
|
|
72
|
-
state.set('increment', (prevState) => ({value: prevState.value + 1}))
|
|
73
|
-
expect(next).toHaveBeenCalledTimes(2) // inital and updated
|
|
74
|
-
expect(next).toHaveBeenLastCalledWith({value: 1})
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('should return a disposable object with a dispose function', () => {
|
|
78
|
-
const resource = createResource<TestState>({
|
|
79
|
-
name: 'test',
|
|
80
|
-
getInitialState: () => ({value: 0}),
|
|
81
|
-
initialize: vi.fn(() => vi.fn()),
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const {dispose} = initializeResource(instance, resource)
|
|
85
|
-
dispose()
|
|
86
|
-
expect(resource.initialize).toHaveReturnedWith(expect.any(Function))
|
|
87
|
-
expect(resource.initialize).toHaveBeenCalledTimes(1)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('should not throw when no dispose function is supplied', () => {
|
|
91
|
-
const resource = createResource<TestState>({
|
|
92
|
-
name: 'test',
|
|
93
|
-
getInitialState: () => ({value: 0}),
|
|
94
|
-
})
|
|
95
|
-
const {dispose} = initializeResource(instance, resource)
|
|
96
|
-
expect(() => dispose()).not.toThrowError()
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('should clear resource caqche on disposeResources', () => {
|
|
100
|
-
const resource = createResource<TestState>({
|
|
101
|
-
name: 'test',
|
|
102
|
-
getInitialState: () => ({value: 0}),
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
getOrCreateResource(instance, resource)
|
|
106
|
-
disposeResources(instance.identity)
|
|
107
|
-
|
|
108
|
-
const {state} = initializeResource(instance, resource)
|
|
109
|
-
|
|
110
|
-
expect(state.get()).toEqual({value: 0})
|
|
111
|
-
})
|
|
112
|
-
})
|