@spoosh/react 0.6.0 → 0.7.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.
- package/README.md +2 -24
- package/dist/index.d.mts +67 -86
- package/dist/index.d.ts +67 -86
- package/dist/index.js +116 -173
- package/dist/index.mjs +115 -182
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -53,10 +53,11 @@ function createUseRead(options) {
|
|
|
53
53
|
initialized: false,
|
|
54
54
|
prevContext: null
|
|
55
55
|
});
|
|
56
|
-
|
|
56
|
+
const baseQueryKeyChanged = controllerRef.current && controllerRef.current.baseQueryKey !== queryKey;
|
|
57
|
+
if (baseQueryKeyChanged) {
|
|
57
58
|
lifecycleRef.current.prevContext = controllerRef.current.controller.getContext();
|
|
58
59
|
}
|
|
59
|
-
if (!controllerRef.current ||
|
|
60
|
+
if (!controllerRef.current || baseQueryKeyChanged) {
|
|
60
61
|
const controller2 = createOperationController({
|
|
61
62
|
operationType: "read",
|
|
62
63
|
path: pathSegments,
|
|
@@ -73,29 +74,36 @@ function createUseRead(options) {
|
|
|
73
74
|
return method(fetchOpts);
|
|
74
75
|
}
|
|
75
76
|
});
|
|
76
|
-
controllerRef.current = { controller: controller2, queryKey };
|
|
77
|
+
controllerRef.current = { controller: controller2, queryKey, baseQueryKey: queryKey };
|
|
77
78
|
}
|
|
78
79
|
const controller = controllerRef.current.controller;
|
|
79
80
|
controller.setPluginOptions(pluginOpts);
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
const subscribe = useCallback(
|
|
82
|
+
(callback) => {
|
|
83
|
+
return controller.subscribe(callback);
|
|
84
|
+
},
|
|
85
|
+
[controller]
|
|
84
86
|
);
|
|
87
|
+
const getSnapshot = useCallback(() => {
|
|
88
|
+
return controller.getState();
|
|
89
|
+
}, [controller]);
|
|
90
|
+
const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
85
91
|
const [requestState, setRequestState] = useState(() => {
|
|
86
92
|
const cachedEntry = stateManager.getCache(queryKey);
|
|
87
93
|
const hasCachedData = cachedEntry?.state?.data !== void 0;
|
|
88
94
|
return { isPending: enabled && !hasCachedData, error: void 0 };
|
|
89
95
|
});
|
|
96
|
+
const [, forceUpdate] = useState(0);
|
|
90
97
|
const abortRef = useRef(controller.abort);
|
|
91
98
|
abortRef.current = controller.abort;
|
|
92
99
|
const pluginOptsKey = JSON.stringify(pluginOpts);
|
|
93
100
|
const tagsKey = JSON.stringify(tags);
|
|
94
101
|
const executeWithTracking = useCallback(
|
|
95
|
-
async (force = false) => {
|
|
102
|
+
async (force = false, overrideOptions) => {
|
|
96
103
|
setRequestState((prev) => ({ ...prev, isPending: true }));
|
|
97
104
|
try {
|
|
98
|
-
const
|
|
105
|
+
const execOptions = overrideOptions ? { ...capturedCall.options ?? {}, ...overrideOptions } : void 0;
|
|
106
|
+
const response = await controller.execute(execOptions, { force });
|
|
99
107
|
if (response.error) {
|
|
100
108
|
setRequestState({ isPending: false, error: response.error });
|
|
101
109
|
} else {
|
|
@@ -107,7 +115,7 @@ function createUseRead(options) {
|
|
|
107
115
|
throw err;
|
|
108
116
|
}
|
|
109
117
|
},
|
|
110
|
-
[controller]
|
|
118
|
+
[controller, capturedCall.options]
|
|
111
119
|
);
|
|
112
120
|
useEffect(() => {
|
|
113
121
|
return () => {
|
|
@@ -155,9 +163,84 @@ function createUseRead(options) {
|
|
|
155
163
|
const abort = useCallback(() => {
|
|
156
164
|
abortRef.current();
|
|
157
165
|
}, []);
|
|
158
|
-
const trigger = useCallback(
|
|
159
|
-
|
|
160
|
-
|
|
166
|
+
const trigger = useCallback(
|
|
167
|
+
async (triggerOptions) => {
|
|
168
|
+
const { force = false, ...overrideOptions } = triggerOptions ?? {};
|
|
169
|
+
const hasOverrides = Object.keys(overrideOptions).length > 0;
|
|
170
|
+
if (!hasOverrides) {
|
|
171
|
+
return executeWithTracking(force, void 0);
|
|
172
|
+
}
|
|
173
|
+
const mergedOptions = {
|
|
174
|
+
...capturedCall.options ?? {},
|
|
175
|
+
...overrideOptions
|
|
176
|
+
};
|
|
177
|
+
const newQueryKey = stateManager.createQueryKey({
|
|
178
|
+
path: pathSegments,
|
|
179
|
+
method: capturedCall.method,
|
|
180
|
+
options: mergedOptions
|
|
181
|
+
});
|
|
182
|
+
if (newQueryKey === controllerRef.current?.queryKey) {
|
|
183
|
+
return executeWithTracking(force, overrideOptions);
|
|
184
|
+
}
|
|
185
|
+
const params = mergedOptions?.params;
|
|
186
|
+
const newResolvedPath = resolvePath(pathSegments, params);
|
|
187
|
+
const newResolvedTags = resolveTags({ tags }, newResolvedPath);
|
|
188
|
+
const newController = createOperationController({
|
|
189
|
+
operationType: "read",
|
|
190
|
+
path: pathSegments,
|
|
191
|
+
method: capturedCall.method,
|
|
192
|
+
tags: newResolvedTags,
|
|
193
|
+
requestOptions: mergedOptions,
|
|
194
|
+
stateManager,
|
|
195
|
+
eventEmitter,
|
|
196
|
+
pluginExecutor,
|
|
197
|
+
hookId,
|
|
198
|
+
fetchFn: async (fetchOpts) => {
|
|
199
|
+
const pathMethods = api(capturedCall.path);
|
|
200
|
+
const method = pathMethods[capturedCall.method];
|
|
201
|
+
return method(fetchOpts);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
newController.setPluginOptions(pluginOpts);
|
|
205
|
+
const currentBaseQueryKey = controllerRef.current?.baseQueryKey ?? queryKey;
|
|
206
|
+
controllerRef.current = {
|
|
207
|
+
controller: newController,
|
|
208
|
+
queryKey: newQueryKey,
|
|
209
|
+
baseQueryKey: currentBaseQueryKey
|
|
210
|
+
};
|
|
211
|
+
forceUpdate((n) => n + 1);
|
|
212
|
+
newController.mount();
|
|
213
|
+
setRequestState((prev) => ({ ...prev, isPending: true }));
|
|
214
|
+
try {
|
|
215
|
+
const response = await newController.execute(mergedOptions, {
|
|
216
|
+
force
|
|
217
|
+
});
|
|
218
|
+
if (response.error) {
|
|
219
|
+
setRequestState({ isPending: false, error: response.error });
|
|
220
|
+
} else {
|
|
221
|
+
setRequestState({ isPending: false, error: void 0 });
|
|
222
|
+
}
|
|
223
|
+
return response;
|
|
224
|
+
} catch (err) {
|
|
225
|
+
setRequestState({ isPending: false, error: err });
|
|
226
|
+
throw err;
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
[
|
|
230
|
+
executeWithTracking,
|
|
231
|
+
capturedCall.options,
|
|
232
|
+
capturedCall.method,
|
|
233
|
+
capturedCall.path,
|
|
234
|
+
pathSegments,
|
|
235
|
+
tags,
|
|
236
|
+
stateManager,
|
|
237
|
+
eventEmitter,
|
|
238
|
+
pluginExecutor,
|
|
239
|
+
hookId,
|
|
240
|
+
pluginOpts,
|
|
241
|
+
api
|
|
242
|
+
]
|
|
243
|
+
);
|
|
161
244
|
const entry = stateManager.getCache(queryKey);
|
|
162
245
|
const pluginResultData = entry?.meta ? Object.fromEntries(entry.meta) : {};
|
|
163
246
|
const opts = capturedCall.options;
|
|
@@ -315,160 +398,18 @@ function createUseWrite(options) {
|
|
|
315
398
|
return useWrite;
|
|
316
399
|
}
|
|
317
400
|
|
|
318
|
-
// src/useLazyRead/index.ts
|
|
319
|
-
import {
|
|
320
|
-
useSyncExternalStore as useSyncExternalStore3,
|
|
321
|
-
useRef as useRef3,
|
|
322
|
-
useCallback as useCallback3,
|
|
323
|
-
useState as useState3,
|
|
324
|
-
useId as useId3
|
|
325
|
-
} from "react";
|
|
326
|
-
import {
|
|
327
|
-
createOperationController as createOperationController3,
|
|
328
|
-
createSelectorProxy as createSelectorProxy3,
|
|
329
|
-
resolvePath as resolvePath3
|
|
330
|
-
} from "@spoosh/core";
|
|
331
|
-
function createUseLazyRead(options) {
|
|
332
|
-
const { api, stateManager, pluginExecutor, eventEmitter } = options;
|
|
333
|
-
function useLazyRead(readFn) {
|
|
334
|
-
const hookId = useId3();
|
|
335
|
-
const selectorResultRef = useRef3({
|
|
336
|
-
call: null,
|
|
337
|
-
selector: null
|
|
338
|
-
});
|
|
339
|
-
const selectorProxy = createSelectorProxy3((result) => {
|
|
340
|
-
selectorResultRef.current = result;
|
|
341
|
-
});
|
|
342
|
-
readFn(selectorProxy);
|
|
343
|
-
const selectedEndpoint = selectorResultRef.current.selector;
|
|
344
|
-
if (!selectedEndpoint) {
|
|
345
|
-
throw new Error(
|
|
346
|
-
'useLazyRead requires selecting an HTTP method (GET). Example: useLazyRead((api) => api("posts").GET)'
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
if (selectedEndpoint.method !== "GET") {
|
|
350
|
-
throw new Error(
|
|
351
|
-
"useLazyRead only supports GET method. Use useWrite for POST, PUT, PATCH, DELETE methods."
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
const pathSegments = selectedEndpoint.path.split("/").filter(Boolean);
|
|
355
|
-
const controllerRef = useRef3(null);
|
|
356
|
-
const emptyStateRef = useRef3({ data: void 0, error: void 0 });
|
|
357
|
-
const [currentQueryKey, setCurrentQueryKey] = useState3(null);
|
|
358
|
-
const [, forceUpdate] = useState3(0);
|
|
359
|
-
const getOrCreateController = useCallback3(
|
|
360
|
-
(triggerOptions) => {
|
|
361
|
-
const queryKey = stateManager.createQueryKey({
|
|
362
|
-
path: pathSegments,
|
|
363
|
-
method: selectedEndpoint.method,
|
|
364
|
-
options: triggerOptions
|
|
365
|
-
});
|
|
366
|
-
if (controllerRef.current?.queryKey === queryKey) {
|
|
367
|
-
return { controller: controllerRef.current.controller, queryKey };
|
|
368
|
-
}
|
|
369
|
-
const controller2 = createOperationController3({
|
|
370
|
-
operationType: "read",
|
|
371
|
-
path: pathSegments,
|
|
372
|
-
method: "GET",
|
|
373
|
-
tags: [],
|
|
374
|
-
stateManager,
|
|
375
|
-
eventEmitter,
|
|
376
|
-
pluginExecutor,
|
|
377
|
-
hookId,
|
|
378
|
-
requestOptions: triggerOptions,
|
|
379
|
-
fetchFn: async (fetchOpts) => {
|
|
380
|
-
const pathMethods = api(selectedEndpoint.path);
|
|
381
|
-
const method = pathMethods[selectedEndpoint.method];
|
|
382
|
-
return method(fetchOpts);
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
controllerRef.current = { controller: controller2, queryKey };
|
|
386
|
-
setCurrentQueryKey(queryKey);
|
|
387
|
-
forceUpdate((n) => n + 1);
|
|
388
|
-
return { controller: controller2, queryKey };
|
|
389
|
-
},
|
|
390
|
-
[pathSegments, selectedEndpoint.method, selectedEndpoint.path, hookId]
|
|
391
|
-
);
|
|
392
|
-
const controller = controllerRef.current?.controller;
|
|
393
|
-
const subscribe = useCallback3(
|
|
394
|
-
(callback) => {
|
|
395
|
-
if (!controller) return () => {
|
|
396
|
-
};
|
|
397
|
-
return controller.subscribe(callback);
|
|
398
|
-
},
|
|
399
|
-
[controller]
|
|
400
|
-
);
|
|
401
|
-
const getSnapshot = useCallback3(() => {
|
|
402
|
-
if (!controller) return emptyStateRef.current;
|
|
403
|
-
return controller.getState();
|
|
404
|
-
}, [controller]);
|
|
405
|
-
const state = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
|
|
406
|
-
const [lastTriggerOptions, setLastTriggerOptions] = useState3(void 0);
|
|
407
|
-
const [requestState, setRequestState] = useState3({ isPending: false, error: void 0 });
|
|
408
|
-
const abort = useCallback3(() => {
|
|
409
|
-
controllerRef.current?.controller.abort();
|
|
410
|
-
}, []);
|
|
411
|
-
const trigger = useCallback3(
|
|
412
|
-
async (triggerOptions) => {
|
|
413
|
-
setLastTriggerOptions(triggerOptions);
|
|
414
|
-
setRequestState((prev) => ({ ...prev, isPending: true }));
|
|
415
|
-
const params = triggerOptions?.params;
|
|
416
|
-
resolvePath3(pathSegments, params);
|
|
417
|
-
const { controller: ctrl } = getOrCreateController(triggerOptions);
|
|
418
|
-
ctrl.setPluginOptions(triggerOptions);
|
|
419
|
-
try {
|
|
420
|
-
const response = await ctrl.execute(triggerOptions);
|
|
421
|
-
if (response.error) {
|
|
422
|
-
setRequestState({ isPending: false, error: response.error });
|
|
423
|
-
} else {
|
|
424
|
-
setRequestState({ isPending: false, error: void 0 });
|
|
425
|
-
}
|
|
426
|
-
return response;
|
|
427
|
-
} catch (err) {
|
|
428
|
-
setRequestState({ isPending: false, error: err });
|
|
429
|
-
throw err;
|
|
430
|
-
}
|
|
431
|
-
},
|
|
432
|
-
[pathSegments, getOrCreateController]
|
|
433
|
-
);
|
|
434
|
-
const opts = lastTriggerOptions;
|
|
435
|
-
const inputInner = {};
|
|
436
|
-
if (opts?.query !== void 0) {
|
|
437
|
-
inputInner.query = opts.query;
|
|
438
|
-
}
|
|
439
|
-
if (opts?.body !== void 0) {
|
|
440
|
-
inputInner.body = opts.body;
|
|
441
|
-
}
|
|
442
|
-
if (opts?.params !== void 0) {
|
|
443
|
-
inputInner.params = opts.params;
|
|
444
|
-
}
|
|
445
|
-
const inputField = Object.keys(inputInner).length > 0 ? { input: inputInner } : {};
|
|
446
|
-
const loading = requestState.isPending;
|
|
447
|
-
return {
|
|
448
|
-
trigger,
|
|
449
|
-
...inputField,
|
|
450
|
-
data: state.data,
|
|
451
|
-
error: requestState.error ?? state.error,
|
|
452
|
-
loading,
|
|
453
|
-
abort
|
|
454
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
return useLazyRead;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
401
|
// src/useInfiniteRead/index.ts
|
|
461
402
|
import {
|
|
462
|
-
useRef as
|
|
403
|
+
useRef as useRef3,
|
|
463
404
|
useEffect as useEffect2,
|
|
464
|
-
useSyncExternalStore as
|
|
465
|
-
useId as
|
|
466
|
-
useState as
|
|
405
|
+
useSyncExternalStore as useSyncExternalStore3,
|
|
406
|
+
useId as useId3,
|
|
407
|
+
useState as useState3
|
|
467
408
|
} from "react";
|
|
468
409
|
import {
|
|
469
410
|
createInfiniteReadController,
|
|
470
|
-
createSelectorProxy as
|
|
471
|
-
resolvePath as
|
|
411
|
+
createSelectorProxy as createSelectorProxy3,
|
|
412
|
+
resolvePath as resolvePath3,
|
|
472
413
|
resolveTags as resolveTags3
|
|
473
414
|
} from "@spoosh/core";
|
|
474
415
|
function createUseInfiniteRead(options) {
|
|
@@ -484,12 +425,12 @@ function createUseInfiniteRead(options) {
|
|
|
484
425
|
prevPageRequest,
|
|
485
426
|
...pluginOpts
|
|
486
427
|
} = readOptions;
|
|
487
|
-
const hookId =
|
|
488
|
-
const selectorResultRef =
|
|
428
|
+
const hookId = useId3();
|
|
429
|
+
const selectorResultRef = useRef3({
|
|
489
430
|
call: null,
|
|
490
431
|
selector: null
|
|
491
432
|
});
|
|
492
|
-
const selectorProxy =
|
|
433
|
+
const selectorProxy = createSelectorProxy3((result2) => {
|
|
493
434
|
selectorResultRef.current = result2;
|
|
494
435
|
});
|
|
495
436
|
readFn(selectorProxy);
|
|
@@ -512,13 +453,13 @@ function createUseInfiniteRead(options) {
|
|
|
512
453
|
params: void 0,
|
|
513
454
|
body: void 0
|
|
514
455
|
};
|
|
515
|
-
const resolvedPath =
|
|
456
|
+
const resolvedPath = resolvePath3(pathSegments, requestOptions?.params);
|
|
516
457
|
const resolvedTags = resolveTags3({ tags }, resolvedPath);
|
|
517
|
-
const canFetchNextRef =
|
|
518
|
-
const canFetchPrevRef =
|
|
519
|
-
const nextPageRequestRef =
|
|
520
|
-
const prevPageRequestRef =
|
|
521
|
-
const mergerRef =
|
|
458
|
+
const canFetchNextRef = useRef3(canFetchNext);
|
|
459
|
+
const canFetchPrevRef = useRef3(canFetchPrev);
|
|
460
|
+
const nextPageRequestRef = useRef3(nextPageRequest);
|
|
461
|
+
const prevPageRequestRef = useRef3(prevPageRequest);
|
|
462
|
+
const mergerRef = useRef3(merger);
|
|
522
463
|
canFetchNextRef.current = canFetchNext;
|
|
523
464
|
canFetchPrevRef.current = canFetchPrev;
|
|
524
465
|
nextPageRequestRef.current = nextPageRequest;
|
|
@@ -529,7 +470,7 @@ function createUseInfiniteRead(options) {
|
|
|
529
470
|
method: capturedCall.method,
|
|
530
471
|
options: baseOptionsForKey
|
|
531
472
|
});
|
|
532
|
-
const controllerRef =
|
|
473
|
+
const controllerRef = useRef3(null);
|
|
533
474
|
if (!controllerRef.current || controllerRef.current.queryKey !== queryKey) {
|
|
534
475
|
controllerRef.current = {
|
|
535
476
|
controller: createInfiniteReadController({
|
|
@@ -565,12 +506,12 @@ function createUseInfiniteRead(options) {
|
|
|
565
506
|
}
|
|
566
507
|
const controller = controllerRef.current.controller;
|
|
567
508
|
controller.setPluginOptions(pluginOpts);
|
|
568
|
-
const state =
|
|
509
|
+
const state = useSyncExternalStore3(
|
|
569
510
|
controller.subscribe,
|
|
570
511
|
controller.getState,
|
|
571
512
|
controller.getState
|
|
572
513
|
);
|
|
573
|
-
const [isPending, setIsPending] =
|
|
514
|
+
const [isPending, setIsPending] = useState3(() => {
|
|
574
515
|
return enabled && state.data === void 0;
|
|
575
516
|
});
|
|
576
517
|
const fetchingDirection = controller.getFetchingDirection();
|
|
@@ -579,7 +520,7 @@ function createUseInfiniteRead(options) {
|
|
|
579
520
|
const fetchingPrev = fetchingDirection === "prev";
|
|
580
521
|
const hasData = state.data !== void 0;
|
|
581
522
|
const loading = (isPending || fetching) && !hasData;
|
|
582
|
-
const lifecycleRef =
|
|
523
|
+
const lifecycleRef = useRef3({
|
|
583
524
|
initialized: false,
|
|
584
525
|
prevContext: null
|
|
585
526
|
});
|
|
@@ -662,12 +603,6 @@ function createReactSpoosh(instance) {
|
|
|
662
603
|
eventEmitter,
|
|
663
604
|
pluginExecutor
|
|
664
605
|
});
|
|
665
|
-
const useLazyRead = createUseLazyRead({
|
|
666
|
-
api,
|
|
667
|
-
stateManager,
|
|
668
|
-
eventEmitter,
|
|
669
|
-
pluginExecutor
|
|
670
|
-
});
|
|
671
606
|
const useInfiniteRead = createUseInfiniteRead({
|
|
672
607
|
api,
|
|
673
608
|
stateManager,
|
|
@@ -693,7 +628,6 @@ function createReactSpoosh(instance) {
|
|
|
693
628
|
return {
|
|
694
629
|
useRead,
|
|
695
630
|
useWrite,
|
|
696
|
-
useLazyRead,
|
|
697
631
|
useInfiniteRead,
|
|
698
632
|
...instanceApis
|
|
699
633
|
};
|
|
@@ -701,7 +635,6 @@ function createReactSpoosh(instance) {
|
|
|
701
635
|
export {
|
|
702
636
|
createReactSpoosh,
|
|
703
637
|
createUseInfiniteRead,
|
|
704
|
-
createUseLazyRead,
|
|
705
638
|
createUseRead,
|
|
706
639
|
createUseWrite
|
|
707
640
|
};
|