@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/users/usersStore.ts
CHANGED
|
@@ -1,179 +1,315 @@
|
|
|
1
1
|
import {createSelector} from 'reselect'
|
|
2
|
+
import {
|
|
3
|
+
catchError,
|
|
4
|
+
combineLatest,
|
|
5
|
+
distinctUntilChanged,
|
|
6
|
+
EMPTY,
|
|
7
|
+
filter,
|
|
8
|
+
first,
|
|
9
|
+
firstValueFrom,
|
|
10
|
+
groupBy,
|
|
11
|
+
map,
|
|
12
|
+
mergeMap,
|
|
13
|
+
NEVER,
|
|
14
|
+
Observable,
|
|
15
|
+
of,
|
|
16
|
+
pairwise,
|
|
17
|
+
race,
|
|
18
|
+
skip,
|
|
19
|
+
startWith,
|
|
20
|
+
switchMap,
|
|
21
|
+
tap,
|
|
22
|
+
throwError,
|
|
23
|
+
withLatestFrom,
|
|
24
|
+
} from 'rxjs'
|
|
2
25
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
26
|
+
import {getDashboardOrganizationId} from '../auth/authStore'
|
|
27
|
+
import {getClientState} from '../client/clientStore'
|
|
28
|
+
import {bindActionGlobally} from '../store/createActionBinder'
|
|
29
|
+
import {createStateSourceAction, type SelectorContext} from '../store/createStateSourceAction'
|
|
30
|
+
import {type StoreState} from '../store/createStoreState'
|
|
31
|
+
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
32
|
+
import {insecureRandomId} from '../utils/ids'
|
|
33
|
+
import {
|
|
34
|
+
addSubscription,
|
|
35
|
+
cancelRequest,
|
|
36
|
+
getUsersKey,
|
|
37
|
+
initializeRequest,
|
|
38
|
+
parseUsersKey,
|
|
39
|
+
removeSubscription,
|
|
40
|
+
setUsersData,
|
|
41
|
+
setUsersError,
|
|
42
|
+
updateLastLoadMoreRequest,
|
|
43
|
+
} from './reducers'
|
|
44
|
+
import {
|
|
45
|
+
type GetUsersOptions,
|
|
46
|
+
type ResolveUsersOptions,
|
|
47
|
+
type SanityUserResponse,
|
|
48
|
+
type UsersStoreState,
|
|
49
|
+
} from './types'
|
|
50
|
+
import {API_VERSION, USERS_STATE_CLEAR_DELAY} from './usersConstants'
|
|
12
51
|
|
|
13
52
|
/**
|
|
14
|
-
*
|
|
53
|
+
* The users store resource that manages user data fetching and state.
|
|
54
|
+
*
|
|
55
|
+
* This store handles fetching, caching, and managing user data. It provides functionality for
|
|
56
|
+
* retrieving users associated with specific resources and supports pagination through the
|
|
57
|
+
* `loadMoreUsers` action.
|
|
58
|
+
*
|
|
59
|
+
* @internal
|
|
15
60
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
61
|
+
const usersStore = defineStore<UsersStoreState>({
|
|
62
|
+
name: 'UsersStore',
|
|
63
|
+
getInitialState: () => ({users: {}}),
|
|
64
|
+
initialize: (context) => {
|
|
65
|
+
const subscription = listenForLoadMoreAndFetch(context)
|
|
66
|
+
return () => subscription.unsubscribe()
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const errorHandler =
|
|
71
|
+
(state: StoreState<{error?: unknown}>) =>
|
|
72
|
+
(error: unknown): void =>
|
|
73
|
+
state.set('setError', {error})
|
|
21
74
|
|
|
22
75
|
/**
|
|
23
|
-
*
|
|
76
|
+
* Internal action that listens for new user subscriptions and load more requests.
|
|
77
|
+
* Fetches user data when new subscriptions are added or when loadMoreUsers is called.
|
|
24
78
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
79
|
+
const listenForLoadMoreAndFetch = ({state, instance}: StoreContext<UsersStoreState>) => {
|
|
80
|
+
return state.observable
|
|
81
|
+
.pipe(
|
|
82
|
+
map((s) => new Set(Object.keys(s.users))),
|
|
83
|
+
distinctUntilChanged((curr, next) => {
|
|
84
|
+
if (curr.size !== next.size) return false
|
|
85
|
+
return Array.from(next).every((i) => curr.has(i))
|
|
86
|
+
}),
|
|
87
|
+
startWith(new Set<string>()),
|
|
88
|
+
pairwise(),
|
|
89
|
+
mergeMap(([curr, next]) => {
|
|
90
|
+
const added = Array.from(next).filter((i) => !curr.has(i))
|
|
91
|
+
const removed = Array.from(curr).filter((i) => !next.has(i))
|
|
92
|
+
|
|
93
|
+
return [
|
|
94
|
+
...added.map((key) => ({key, added: true})),
|
|
95
|
+
...removed.map((key) => ({key, added: false})),
|
|
96
|
+
]
|
|
97
|
+
}),
|
|
98
|
+
groupBy((i) => i.key),
|
|
99
|
+
mergeMap((group$) =>
|
|
100
|
+
group$.pipe(
|
|
101
|
+
switchMap((e) => {
|
|
102
|
+
if (!e.added) return EMPTY
|
|
103
|
+
const {batchSize, ...options} = parseUsersKey(group$.key)
|
|
104
|
+
|
|
105
|
+
const projectId = options.projectId ?? instance.config.projectId
|
|
106
|
+
|
|
107
|
+
// the resource type this request will use
|
|
108
|
+
// If resourceType is explicitly provided, use it
|
|
109
|
+
// Otherwise, infer from context: organization if organizationId exists,
|
|
110
|
+
// project if projectId exists, or default to organization
|
|
111
|
+
const resourceType =
|
|
112
|
+
options.resourceType ??
|
|
113
|
+
(options.organizationId ? 'organization' : projectId ? 'project' : 'organization')
|
|
114
|
+
|
|
115
|
+
const organizationId$ = options.organizationId
|
|
116
|
+
? of(options.organizationId)
|
|
117
|
+
: getDashboardOrganizationId(instance).observable.pipe(
|
|
118
|
+
filter((i) => typeof i === 'string'),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
const resource$: Observable<{
|
|
122
|
+
type: 'project' | 'organization'
|
|
123
|
+
id: string
|
|
124
|
+
}> =
|
|
125
|
+
resourceType === 'project'
|
|
126
|
+
? projectId
|
|
127
|
+
? of({type: 'project', id: projectId})
|
|
128
|
+
: throwError(() => new Error('Project ID required for this API.'))
|
|
129
|
+
: organizationId$.pipe(map((id) => ({type: 'organization', id})))
|
|
130
|
+
|
|
131
|
+
const client$ = getClientState(instance, {
|
|
132
|
+
scope: 'global',
|
|
133
|
+
apiVersion: API_VERSION,
|
|
134
|
+
}).observable
|
|
135
|
+
|
|
136
|
+
const loadMore$ = state.observable.pipe(
|
|
137
|
+
map((s) => s.users[group$.key]?.lastLoadMoreRequest),
|
|
138
|
+
distinctUntilChanged(),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
const cursor$ = state.observable.pipe(
|
|
142
|
+
map((s) => s.users[group$.key]?.nextCursor),
|
|
143
|
+
distinctUntilChanged(),
|
|
144
|
+
filter((cursor) => cursor !== null),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return combineLatest([resource$, client$, loadMore$]).pipe(
|
|
148
|
+
withLatestFrom(cursor$),
|
|
149
|
+
switchMap(([[resource, client], cursor]) =>
|
|
150
|
+
client.observable.request<SanityUserResponse>({
|
|
151
|
+
method: 'GET',
|
|
152
|
+
uri: `access/${resource.type}/${resource.id}/users`,
|
|
153
|
+
query: cursor
|
|
154
|
+
? {nextCursor: cursor, limit: batchSize.toString()}
|
|
155
|
+
: {limit: batchSize.toString()},
|
|
156
|
+
}),
|
|
157
|
+
),
|
|
158
|
+
catchError((error) => {
|
|
159
|
+
state.set('setUsersError', setUsersError(group$.key, error))
|
|
160
|
+
return EMPTY
|
|
161
|
+
}),
|
|
162
|
+
tap((response) => state.set('setUsersData', setUsersData(group$.key, response))),
|
|
163
|
+
)
|
|
164
|
+
}),
|
|
165
|
+
),
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
.subscribe({error: errorHandler(state)})
|
|
39
169
|
}
|
|
40
170
|
|
|
41
171
|
/**
|
|
42
|
-
*
|
|
172
|
+
* Returns the state source for users associated with a specific resource.
|
|
173
|
+
*
|
|
174
|
+
* This function returns a state source that represents the current list of users for a given
|
|
175
|
+
* resource. Subscribing to the state source will instruct the SDK to fetch the users (if not
|
|
176
|
+
* already fetched) and will load more from this state source as well. When the last subscriber is
|
|
177
|
+
* removed, the users state is automatically cleaned up from the store after a delay.
|
|
178
|
+
*
|
|
179
|
+
* Note: This functionality is for advanced users who want to build their own framework
|
|
180
|
+
* integrations. Our SDK also provides a React integration for convenient usage.
|
|
181
|
+
*
|
|
182
|
+
* @beta
|
|
43
183
|
*/
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
184
|
+
export const getUsersState = bindActionGlobally(
|
|
185
|
+
usersStore,
|
|
186
|
+
createStateSourceAction({
|
|
187
|
+
selector: createSelector(
|
|
188
|
+
[
|
|
189
|
+
({instance, state}: SelectorContext<UsersStoreState>, options?: GetUsersOptions) =>
|
|
190
|
+
state.error ?? state.users[getUsersKey(instance, options)]?.error,
|
|
191
|
+
({instance, state}: SelectorContext<UsersStoreState>, options: GetUsersOptions) =>
|
|
192
|
+
state.users[getUsersKey(instance, options)]?.users,
|
|
193
|
+
({instance, state}: SelectorContext<UsersStoreState>, options: GetUsersOptions) =>
|
|
194
|
+
state.users[getUsersKey(instance, options)]?.totalCount,
|
|
195
|
+
({instance, state}: SelectorContext<UsersStoreState>, options: GetUsersOptions) =>
|
|
196
|
+
state.users[getUsersKey(instance, options)]?.nextCursor,
|
|
197
|
+
],
|
|
198
|
+
(error, data, totalCount, nextCursor) => {
|
|
199
|
+
if (error) throw error
|
|
200
|
+
if (data === undefined || totalCount === undefined || nextCursor === undefined) {
|
|
201
|
+
return undefined
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {data, totalCount, hasMore: nextCursor !== null}
|
|
205
|
+
},
|
|
206
|
+
),
|
|
207
|
+
onSubscribe: ({instance, state}, options?: GetUsersOptions) => {
|
|
208
|
+
const subscriptionId = insecureRandomId()
|
|
209
|
+
const key = getUsersKey(instance, options)
|
|
210
|
+
state.set('addSubscription', addSubscription(subscriptionId, key))
|
|
211
|
+
return () => {
|
|
212
|
+
setTimeout(
|
|
213
|
+
() => state.set('removeSubscription', removeSubscription(subscriptionId, key)),
|
|
214
|
+
USERS_STATE_CLEAR_DELAY,
|
|
215
|
+
)
|
|
216
|
+
}
|
|
56
217
|
},
|
|
57
218
|
}),
|
|
58
|
-
initialize() {
|
|
59
|
-
return () => {}
|
|
60
|
-
},
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* @public
|
|
65
|
-
*/
|
|
66
|
-
const getState = createStateSourceAction(
|
|
67
|
-
usersStore,
|
|
68
|
-
createSelector(
|
|
69
|
-
[
|
|
70
|
-
(state: UsersStoreState) => state.users,
|
|
71
|
-
(state: UsersStoreState) => state.totalCount,
|
|
72
|
-
(state: UsersStoreState) => state.nextCursor,
|
|
73
|
-
(state: UsersStoreState) => state.hasMore,
|
|
74
|
-
(state: UsersStoreState) => state.initialFetchCompleted,
|
|
75
|
-
(state: UsersStoreState) => state.options,
|
|
76
|
-
],
|
|
77
|
-
(users, totalCount, nextCursor, hasMore, initialFetchCompleted, options) => ({
|
|
78
|
-
users,
|
|
79
|
-
totalCount,
|
|
80
|
-
nextCursor,
|
|
81
|
-
hasMore,
|
|
82
|
-
options,
|
|
83
|
-
initialFetchCompleted,
|
|
84
|
-
}),
|
|
85
|
-
),
|
|
86
219
|
)
|
|
87
220
|
|
|
88
|
-
interface FetchUsersParams {
|
|
89
|
-
resourceType: 'organization' | 'project'
|
|
90
|
-
resourceId: string
|
|
91
|
-
nextCursor?: string | null
|
|
92
|
-
limit?: number
|
|
93
|
-
}
|
|
94
|
-
|
|
95
221
|
/**
|
|
96
|
-
*
|
|
222
|
+
* Resolves the users for a specific resource without registering a lasting subscriber.
|
|
223
|
+
*
|
|
224
|
+
* This function fetches the users for a given resource and returns a promise that resolves with
|
|
225
|
+
* the users result. Unlike `getUsersState`, which registers subscribers to keep the data live and
|
|
226
|
+
* performs automatic cleanup, `resolveUsers` does not track subscribers. This makes it ideal for
|
|
227
|
+
* use with React Suspense, where the returned promise is thrown to delay rendering until the users
|
|
228
|
+
* result becomes available. Once the promise resolves, it is expected that a real subscriber will
|
|
229
|
+
* be added via `getUsersState` to manage ongoing updates.
|
|
230
|
+
*
|
|
231
|
+
* Additionally, an optional AbortSignal can be provided to cancel the request and immediately
|
|
232
|
+
* clear the associated state if there are no active subscribers.
|
|
233
|
+
*
|
|
234
|
+
* @beta
|
|
97
235
|
*/
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
method: 'GET',
|
|
104
|
-
uri: `access/${resourceType}/${resourceId}/users`,
|
|
105
|
-
query: nextCursor ? {nextCursor, limit: limit.toString()} : {limit: limit.toString()},
|
|
106
|
-
tag: 'users',
|
|
107
|
-
})
|
|
108
|
-
}
|
|
236
|
+
export const resolveUsers = bindActionGlobally(
|
|
237
|
+
usersStore,
|
|
238
|
+
async ({state, instance}, {signal, ...options}: ResolveUsersOptions) => {
|
|
239
|
+
const key = getUsersKey(instance, options)
|
|
240
|
+
const {getCurrent} = getUsersState(instance, options)
|
|
109
241
|
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
242
|
+
const aborted$ = signal
|
|
243
|
+
? new Observable<never>((observer) => {
|
|
244
|
+
const cleanup = () => {
|
|
245
|
+
signal.removeEventListener('abort', listener)
|
|
246
|
+
}
|
|
114
247
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
248
|
+
const listener = () => {
|
|
249
|
+
observer.error(new DOMException('The operation was aborted.', 'AbortError'))
|
|
250
|
+
observer.complete()
|
|
251
|
+
cleanup()
|
|
252
|
+
}
|
|
253
|
+
signal.addEventListener('abort', listener)
|
|
118
254
|
|
|
119
|
-
|
|
255
|
+
return cleanup
|
|
256
|
+
}).pipe(
|
|
257
|
+
catchError((error) => {
|
|
258
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
259
|
+
state.set('cancelRequest', cancelRequest(key))
|
|
260
|
+
}
|
|
261
|
+
throw error
|
|
262
|
+
}),
|
|
263
|
+
)
|
|
264
|
+
: NEVER
|
|
120
265
|
|
|
121
|
-
|
|
122
|
-
const hasMore = allUsers.length < response.totalCount
|
|
266
|
+
state.set('initializeRequest', initializeRequest(key))
|
|
123
267
|
|
|
124
|
-
state.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
hasMore,
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
})
|
|
268
|
+
const resolved$ = state.observable.pipe(
|
|
269
|
+
map(getCurrent),
|
|
270
|
+
first((i) => i !== undefined),
|
|
271
|
+
)
|
|
132
272
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const {resourceType, resourceId, limit} = options
|
|
273
|
+
return firstValueFrom(race([resolved$, aborted$]))
|
|
274
|
+
},
|
|
275
|
+
)
|
|
137
276
|
|
|
138
|
-
|
|
139
|
-
|
|
277
|
+
/**
|
|
278
|
+
* Loads more users for a specific resource.
|
|
279
|
+
*
|
|
280
|
+
* This function triggers a request to fetch the next page of users for a given resource. It
|
|
281
|
+
* requires that users have already been loaded for the resource (via `resolveUsers` or
|
|
282
|
+
* `getUsersState`), and that there are more users available to load (as indicated by the `hasMore`
|
|
283
|
+
* property).
|
|
284
|
+
*
|
|
285
|
+
* The function returns a promise that resolves when the next page of users has been loaded.
|
|
286
|
+
*
|
|
287
|
+
* @beta
|
|
288
|
+
*/
|
|
289
|
+
export const loadMoreUsers = bindActionGlobally(
|
|
290
|
+
usersStore,
|
|
291
|
+
async ({state, instance}, options?: GetUsersOptions) => {
|
|
292
|
+
const key = getUsersKey(instance, options)
|
|
293
|
+
const users = getUsersState(instance, options)
|
|
294
|
+
const usersState = users.getCurrent()
|
|
295
|
+
if (!usersState) {
|
|
296
|
+
throw new Error('Users not loaded for specified resource. Please call resolveUsers first.')
|
|
140
297
|
}
|
|
141
298
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
state.set('resolveUsers', {
|
|
147
|
-
users: response.data,
|
|
148
|
-
totalCount: response.totalCount,
|
|
149
|
-
nextCursor: response.nextCursor,
|
|
150
|
-
hasMore,
|
|
151
|
-
initialFetchCompleted: true,
|
|
152
|
-
})
|
|
299
|
+
if (!usersState.hasMore) {
|
|
300
|
+
throw new Error('No more users available to load for this resource.')
|
|
301
|
+
}
|
|
153
302
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
303
|
+
const promise = firstValueFrom(
|
|
304
|
+
users.observable.pipe(
|
|
305
|
+
filter((i) => i !== undefined),
|
|
306
|
+
skip(1),
|
|
307
|
+
),
|
|
308
|
+
)
|
|
157
309
|
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
state.set('options', {
|
|
161
|
-
...state.get(),
|
|
162
|
-
options: {
|
|
163
|
-
...state.get().options,
|
|
164
|
-
resourceType: options.resourceType,
|
|
165
|
-
resourceId: options.resourceId,
|
|
166
|
-
},
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
})
|
|
310
|
+
const timestamp = new Date().toISOString()
|
|
311
|
+
state.set('updateLastLoadMoreRequest', updateLastLoadMoreRequest(timestamp, key))
|
|
170
312
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
export const createUsersStore = createStore(usersStore, {
|
|
175
|
-
getState,
|
|
176
|
-
loadMore,
|
|
177
|
-
resolveUsers,
|
|
178
|
-
setOptions,
|
|
179
|
-
})
|
|
313
|
+
return await promise
|
|
314
|
+
},
|
|
315
|
+
)
|
|
@@ -2,8 +2,7 @@ import {delay, firstValueFrom, of, throwError} from 'rxjs'
|
|
|
2
2
|
import {filter, skip} from 'rxjs/operators'
|
|
3
3
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
4
4
|
|
|
5
|
-
import {createSanityInstance} from '../
|
|
6
|
-
import {type SanityInstance} from '../instance/types'
|
|
5
|
+
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
7
6
|
import {createFetcherStore} from './createFetcherStore'
|
|
8
7
|
|
|
9
8
|
describe('createFetcherStore', () => {
|
|
@@ -22,7 +21,7 @@ describe('createFetcherStore', () => {
|
|
|
22
21
|
const store = createFetcherStore({
|
|
23
22
|
name: 'test',
|
|
24
23
|
fetcher: () => (param: number) => of(`data-${param}`).pipe(delay(100)),
|
|
25
|
-
getKey: (param: number) => `key-${param}`,
|
|
24
|
+
getKey: (_instance, param: number) => `key-${param}`,
|
|
26
25
|
})
|
|
27
26
|
|
|
28
27
|
const stateSource = store.getState(instance, 1)
|
|
@@ -44,7 +43,7 @@ describe('createFetcherStore', () => {
|
|
|
44
43
|
const store = createFetcherStore({
|
|
45
44
|
name: 'test-throttle',
|
|
46
45
|
fetcher: () => fetchSpy,
|
|
47
|
-
getKey: (param: number) => `key-${param}`,
|
|
46
|
+
getKey: (_instance, param: number) => `key-${param}`,
|
|
48
47
|
fetchThrottleInternal: 1000,
|
|
49
48
|
})
|
|
50
49
|
|
|
@@ -85,7 +84,7 @@ describe('createFetcherStore', () => {
|
|
|
85
84
|
const store = createFetcherStore({
|
|
86
85
|
name: 'test-expiration',
|
|
87
86
|
fetcher: () => fetchSpy,
|
|
88
|
-
getKey: (param: number) => `key-${param}`,
|
|
87
|
+
getKey: (_instance, param: number) => `key-${param}`,
|
|
89
88
|
stateExpirationDelay: 1000,
|
|
90
89
|
})
|
|
91
90
|
|
|
@@ -116,7 +115,7 @@ describe('createFetcherStore', () => {
|
|
|
116
115
|
const store = createFetcherStore({
|
|
117
116
|
name: 'test-throttle',
|
|
118
117
|
fetcher: () => fetchSpy,
|
|
119
|
-
getKey: (param: number) => `key-${param}`,
|
|
118
|
+
getKey: (_instance, param: number) => `key-${param}`,
|
|
120
119
|
fetchThrottleInternal: 1000,
|
|
121
120
|
})
|
|
122
121
|
|
|
@@ -151,7 +150,7 @@ describe('createFetcherStore', () => {
|
|
|
151
150
|
const store = createFetcherStore({
|
|
152
151
|
name: 'test-params',
|
|
153
152
|
fetcher: () => (param: number) => of(`data-${param}`),
|
|
154
|
-
getKey: (param: number) => `key-${param}`,
|
|
153
|
+
getKey: (_instance, param: number) => `key-${param}`,
|
|
155
154
|
})
|
|
156
155
|
|
|
157
156
|
const stateSource1 = store.getState(instance, 1)
|