@civet/core 1.4.3 → 2.0.0

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.
@@ -1,321 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
2
- import AbortSignal from './AbortSignal';
3
- import Meta from './Meta';
4
- import { useConfigContext } from './context';
5
- import uniqueIdentifier from './uniqueIdentifier';
6
-
7
- /**
8
- * Appends a new requestInstruction to the state, causing the resource to fetch data.
9
- **/
10
- function createRequestInstruction(state) {
11
- return {
12
- ...state,
13
- requestInstruction: {
14
- dataProvider: state.dataProvider,
15
- requestDetails: state.requestDetails,
16
- request: state.request,
17
- revision: state.revision,
18
- value: state.value,
19
- },
20
- };
21
- }
22
-
23
- /**
24
- * State reducer for the resource.
25
- */
26
- function reducer(state, action) {
27
- switch (action.type) {
28
- // Creates a new request and instructs the resource to fetch data.
29
- case 'next-request': {
30
- const { requestDetails: nextRequestDetails, persistent: nextPersistent } =
31
- action;
32
- const nextRequest = uniqueIdentifier(state.request);
33
- const nextRevision = uniqueIdentifier(state.revision);
34
- let isPersistent = false;
35
- if (state.persistent === 'very' && nextPersistent === 'very') {
36
- isPersistent = 'very';
37
- } else if (state.persistent && nextPersistent) {
38
- isPersistent = true;
39
- }
40
- const shouldValuePersist =
41
- !nextRequestDetails.empty &&
42
- state.dataProvider.shouldPersist(
43
- nextRequestDetails,
44
- state.requestDetails,
45
- isPersistent,
46
- state.value,
47
- );
48
- return createRequestInstruction({
49
- dataProvider: state.dataProvider,
50
- requestDetails: nextRequestDetails,
51
- request: nextRequest,
52
- revision: nextRevision,
53
- isLoading: !nextRequestDetails.empty,
54
- value: shouldValuePersist
55
- ? state.value
56
- : {
57
- name: nextRequestDetails.name,
58
- query: nextRequestDetails.query,
59
- options: nextRequestDetails.options,
60
- request: nextRequest,
61
- revision: nextRevision,
62
- data: [],
63
- meta: {},
64
- error: undefined,
65
- isEmpty: !!nextRequestDetails.empty,
66
- isIncomplete: !nextRequestDetails.empty,
67
- isInitial: !nextRequestDetails.empty,
68
- },
69
- persistent: nextPersistent,
70
- });
71
- }
72
-
73
- // Creates a new revision for the current request and instructs the resource to fetch data.
74
- case 'next-revision': {
75
- const { notify } = action;
76
- const nextRevision = uniqueIdentifier(state.revision);
77
- notify({ request: state.request, revison: nextRevision });
78
- return createRequestInstruction({
79
- ...state,
80
- revision: nextRevision,
81
- isLoading: !state.requestDetails.empty,
82
- });
83
- }
84
-
85
- // Sets a new persistence level.
86
- case 'set-persistence': {
87
- const { persistent: nextPersistent } = action;
88
- return {
89
- ...state,
90
- persistent: nextPersistent,
91
- };
92
- }
93
-
94
- // Updates the current request's data.
95
- case 'update-data': {
96
- const { request, revision, value } = action;
97
- if (request !== state.request || revision !== state.revision) {
98
- return state;
99
- }
100
- return {
101
- ...state,
102
- isLoading: value.isIncomplete,
103
- value,
104
- };
105
- }
106
- }
107
-
108
- return state;
109
- }
110
-
111
- /**
112
- * Starts fetching data and updates the resource when new data is available.
113
- */
114
- function fetchData(requestInstruction, instance, abortSignal, dispatch) {
115
- const { dataProvider, requestDetails, request, revision, value } =
116
- requestInstruction;
117
-
118
- const meta = new Meta({ ...value.meta }, instance);
119
-
120
- let promise = Promise.resolve(value);
121
-
122
- const callback = (error, done, data) => {
123
- promise = promise.then((prevValue) => {
124
- try {
125
- let nextValue;
126
- if (error != null) {
127
- nextValue = { ...prevValue, error, isIncomplete: false };
128
- } else {
129
- const context = {
130
- name: requestDetails.name,
131
- query: requestDetails.query,
132
- options: requestDetails.options,
133
- request,
134
- revision,
135
- data,
136
- meta: meta.commit(prevValue.meta),
137
- error: undefined,
138
- isEmpty: false,
139
- isIncomplete: !done,
140
- isInitial: !!prevValue.isInitial && !done,
141
- };
142
- context.data = dataProvider.transition(context, prevValue);
143
- context.data = dataProvider.recycleItems(context, prevValue);
144
- nextValue = context;
145
- }
146
-
147
- dispatch({ type: 'update-data', request, revision, value: nextValue });
148
-
149
- return nextValue;
150
- } catch {
151
- return prevValue;
152
- }
153
- });
154
- };
155
-
156
- dataProvider.continuousGet(
157
- requestDetails.name,
158
- requestDetails.query,
159
- requestDetails.options,
160
- meta,
161
- callback,
162
- abortSignal,
163
- );
164
- }
165
-
166
- /**
167
- * Provides data based on the given request details and DataProvider.
168
- *
169
- * Necessary configuration that is not directly specified is taken from the ConfigContext.
170
- *
171
- * The provided DataProvider must not be changed.
172
- */
173
- function useResource({
174
- /** DataProvider to be used for requests - must not be changed */
175
- dataProvider: dataProviderProp,
176
- /** Resource name */
177
- name: nextName,
178
- /** Query instructions */
179
- query: nextQuery,
180
- /** Disables fetching data, resulting in an empty data array */
181
- empty: nextEmpty,
182
- /** Query options for requests */
183
- options: nextOptions,
184
- /** Whether stale data should be retained during the next request - this only applies if name did not change, unless set to "very" */
185
- persistent: nextPersistent,
186
- ...rest
187
- }) {
188
- const configContext = useConfigContext();
189
- const currentDataProvider = dataProviderProp || configContext.dataProvider;
190
-
191
- const nextRequestDetails = useMemo(
192
- () => ({
193
- name: nextName,
194
- query: nextQuery,
195
- empty: nextEmpty,
196
- options: nextOptions,
197
- }),
198
- [nextName, nextQuery, nextEmpty, nextOptions],
199
- );
200
- const [state, dispatch] = useReducer(reducer, undefined, () => {
201
- const request = uniqueIdentifier();
202
- const revision = uniqueIdentifier();
203
- return createRequestInstruction({
204
- dataProvider: currentDataProvider,
205
- requestDetails: nextRequestDetails,
206
- request,
207
- revision,
208
- isLoading: !nextRequestDetails.empty,
209
- value: {
210
- name: nextRequestDetails.name,
211
- query: nextRequestDetails.query,
212
- options: nextRequestDetails.options,
213
- request,
214
- revision,
215
- data: [],
216
- meta: {},
217
- error: undefined,
218
- isEmpty: !!nextRequestDetails.empty,
219
- isIncomplete: !nextRequestDetails.empty,
220
- isInitial: !nextRequestDetails.empty,
221
- },
222
- persistent: nextPersistent,
223
- });
224
- });
225
- const {
226
- dataProvider,
227
- requestDetails,
228
- request,
229
- revision,
230
- isLoading,
231
- value,
232
- persistent,
233
- requestInstruction,
234
- } = state;
235
-
236
- if (dataProvider == null) {
237
- throw new Error(
238
- 'Unmet requirement: The DataProvider for the useResource hook is missing - Check your ConfigContext provider and the dataProvider property',
239
- );
240
- }
241
- if (dataProvider !== currentDataProvider) {
242
- throw new Error(
243
- 'Constant violation: The DataProvider provided to the useResource hook must not be replaced - Check your ConfigContext provider and the dataProvider property',
244
- );
245
- }
246
-
247
- const [instance, setInstance] = useState();
248
- useEffect(() => {
249
- const i = dataProvider.createInstance();
250
- setInstance(i ?? {});
251
- return () => {
252
- dataProvider.releaseInstance(i);
253
- };
254
- }, [dataProvider]);
255
-
256
- if (
257
- requestDetails !== nextRequestDetails &&
258
- !dataProvider.compareRequests(nextRequestDetails, requestDetails)
259
- ) {
260
- dispatch({
261
- type: 'next-request',
262
- requestDetails: nextRequestDetails,
263
- persistent: nextPersistent,
264
- });
265
- } else if (persistent !== nextPersistent) {
266
- dispatch({ type: 'set-persistence', persistent: nextPersistent });
267
- }
268
-
269
- const notify = useCallback(
270
- async () =>
271
- new Promise((resolve) => {
272
- dispatch({ type: 'next-revision', notify: resolve });
273
- }),
274
- [],
275
- );
276
-
277
- // DataProvider events
278
- useEffect(() => {
279
- if (requestDetails.empty) return undefined;
280
-
281
- const unsubscribe = dataProvider.subscribe(requestDetails.name, notify);
282
- return unsubscribe;
283
- }, [requestDetails.empty, dataProvider, requestDetails.name, notify]);
284
-
285
- // Fetch data when instructed
286
- useEffect(() => {
287
- if (instance == null || requestInstruction.requestDetails.empty) {
288
- return undefined;
289
- }
290
-
291
- const abortSignal = new AbortSignal();
292
-
293
- // Start fetching data.
294
- fetchData(requestInstruction, instance, abortSignal, dispatch);
295
-
296
- return () => {
297
- // Abort fetching data when another request is pending or the React component is unmounted.
298
- abortSignal.abort();
299
- };
300
- }, [instance, requestInstruction]);
301
-
302
- const isStale = revision !== value.revision;
303
- const nextRequest = isStale ? request : value.request;
304
- const nextRevision = isStale ? revision : value.revision;
305
- const next = useMemo(
306
- () => ({ request: nextRequest, revision: nextRevision }),
307
- [nextRequest, nextRevision],
308
- );
309
- const context = useMemo(
310
- () => ({ ...value, dataProvider, isLoading, isStale, next, notify }),
311
- [value, dataProvider, isLoading, isStale, next, notify],
312
- );
313
-
314
- // Apply context plugins and return the final context.
315
- return dataProvider.contextPlugins.reduce(
316
- (result, fn) => fn(result, rest),
317
- context,
318
- );
319
- }
320
-
321
- export default useResource;