@spoosh/react 0.5.0 → 0.6.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 +26 -5
- package/dist/index.d.mts +107 -62
- package/dist/index.d.ts +107 -62
- package/dist/index.js +187 -43
- package/dist/index.mjs +196 -42
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -18,15 +18,19 @@ import {
|
|
|
18
18
|
} from "@spoosh/core";
|
|
19
19
|
function createUseRead(options) {
|
|
20
20
|
const { api, stateManager, eventEmitter, pluginExecutor } = options;
|
|
21
|
-
|
|
22
|
-
const {
|
|
21
|
+
function useRead(readFn, readOptions) {
|
|
22
|
+
const {
|
|
23
|
+
enabled = true,
|
|
24
|
+
tags,
|
|
25
|
+
...pluginOpts
|
|
26
|
+
} = readOptions ?? {};
|
|
23
27
|
const hookId = useId();
|
|
24
28
|
const selectorResultRef = useRef({
|
|
25
29
|
call: null,
|
|
26
30
|
selector: null
|
|
27
31
|
});
|
|
28
|
-
const selectorProxy = createSelectorProxy((
|
|
29
|
-
selectorResultRef.current =
|
|
32
|
+
const selectorProxy = createSelectorProxy((result) => {
|
|
33
|
+
selectorResultRef.current = result;
|
|
30
34
|
});
|
|
31
35
|
readFn(selectorProxy);
|
|
32
36
|
const capturedCall = selectorResultRef.current.call;
|
|
@@ -86,6 +90,7 @@ function createUseRead(options) {
|
|
|
86
90
|
const abortRef = useRef(controller.abort);
|
|
87
91
|
abortRef.current = controller.abort;
|
|
88
92
|
const pluginOptsKey = JSON.stringify(pluginOpts);
|
|
93
|
+
const tagsKey = JSON.stringify(tags);
|
|
89
94
|
const executeWithTracking = useCallback(
|
|
90
95
|
async (force = false) => {
|
|
91
96
|
setRequestState((prev) => ({ ...prev, isPending: true }));
|
|
@@ -141,7 +146,7 @@ function createUseRead(options) {
|
|
|
141
146
|
unsubRefetch();
|
|
142
147
|
unsubInvalidate();
|
|
143
148
|
};
|
|
144
|
-
}, [queryKey, enabled]);
|
|
149
|
+
}, [queryKey, enabled, tagsKey]);
|
|
145
150
|
useEffect(() => {
|
|
146
151
|
if (!enabled || !lifecycleRef.current.initialized) return;
|
|
147
152
|
const prevContext = controller.getContext();
|
|
@@ -150,7 +155,7 @@ function createUseRead(options) {
|
|
|
150
155
|
const abort = useCallback(() => {
|
|
151
156
|
abortRef.current();
|
|
152
157
|
}, []);
|
|
153
|
-
const
|
|
158
|
+
const trigger = useCallback(() => {
|
|
154
159
|
return executeWithTracking(true);
|
|
155
160
|
}, [executeWithTracking]);
|
|
156
161
|
const entry = stateManager.getCache(queryKey);
|
|
@@ -170,7 +175,8 @@ function createUseRead(options) {
|
|
|
170
175
|
const hasData = state.data !== void 0;
|
|
171
176
|
const loading = requestState.isPending && !hasData;
|
|
172
177
|
const fetching = requestState.isPending;
|
|
173
|
-
|
|
178
|
+
return {
|
|
179
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
174
180
|
meta: pluginResultData,
|
|
175
181
|
...inputField,
|
|
176
182
|
data: state.data,
|
|
@@ -178,10 +184,10 @@ function createUseRead(options) {
|
|
|
178
184
|
loading,
|
|
179
185
|
fetching,
|
|
180
186
|
abort,
|
|
181
|
-
|
|
187
|
+
trigger
|
|
182
188
|
};
|
|
183
|
-
|
|
184
|
-
|
|
189
|
+
}
|
|
190
|
+
return useRead;
|
|
185
191
|
}
|
|
186
192
|
|
|
187
193
|
// src/useWrite/index.ts
|
|
@@ -200,14 +206,14 @@ import {
|
|
|
200
206
|
} from "@spoosh/core";
|
|
201
207
|
function createUseWrite(options) {
|
|
202
208
|
const { api, stateManager, pluginExecutor, eventEmitter } = options;
|
|
203
|
-
|
|
209
|
+
function useWrite(writeFn) {
|
|
204
210
|
const hookId = useId2();
|
|
205
211
|
const selectorResultRef = useRef2({
|
|
206
212
|
call: null,
|
|
207
213
|
selector: null
|
|
208
214
|
});
|
|
209
|
-
const selectorProxy = createSelectorProxy2((
|
|
210
|
-
selectorResultRef.current =
|
|
215
|
+
const selectorProxy = createSelectorProxy2((result) => {
|
|
216
|
+
selectorResultRef.current = result;
|
|
211
217
|
});
|
|
212
218
|
writeFn(selectorProxy);
|
|
213
219
|
const selectedEndpoint = selectorResultRef.current.selector;
|
|
@@ -251,10 +257,6 @@ function createUseWrite(options) {
|
|
|
251
257
|
);
|
|
252
258
|
const [lastTriggerOptions, setLastTriggerOptions] = useState2(void 0);
|
|
253
259
|
const [requestState, setRequestState] = useState2({ isPending: false, error: void 0 });
|
|
254
|
-
const reset = useCallback2(() => {
|
|
255
|
-
stateManager.deleteCache(queryKey);
|
|
256
|
-
setRequestState({ isPending: false, error: void 0 });
|
|
257
|
-
}, [queryKey]);
|
|
258
260
|
const abort = useCallback2(() => {
|
|
259
261
|
controller.abort();
|
|
260
262
|
}, []);
|
|
@@ -298,32 +300,175 @@ function createUseWrite(options) {
|
|
|
298
300
|
}
|
|
299
301
|
const inputField = Object.keys(inputInner).length > 0 ? { input: inputInner } : {};
|
|
300
302
|
const loading = requestState.isPending;
|
|
301
|
-
|
|
303
|
+
return {
|
|
302
304
|
trigger,
|
|
305
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
303
306
|
meta: pluginResultData,
|
|
304
307
|
...inputField,
|
|
305
308
|
data: state.data,
|
|
306
309
|
error: requestState.error ?? state.error,
|
|
307
310
|
loading,
|
|
308
|
-
reset,
|
|
309
311
|
abort
|
|
312
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
310
313
|
};
|
|
311
|
-
|
|
312
|
-
|
|
314
|
+
}
|
|
315
|
+
return useWrite;
|
|
313
316
|
}
|
|
314
317
|
|
|
315
|
-
// src/
|
|
318
|
+
// src/useLazyRead/index.ts
|
|
316
319
|
import {
|
|
320
|
+
useSyncExternalStore as useSyncExternalStore3,
|
|
317
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
|
+
// src/useInfiniteRead/index.ts
|
|
461
|
+
import {
|
|
462
|
+
useRef as useRef4,
|
|
318
463
|
useEffect as useEffect2,
|
|
319
|
-
useSyncExternalStore as
|
|
320
|
-
useId as
|
|
321
|
-
useState as
|
|
464
|
+
useSyncExternalStore as useSyncExternalStore4,
|
|
465
|
+
useId as useId4,
|
|
466
|
+
useState as useState4
|
|
322
467
|
} from "react";
|
|
323
468
|
import {
|
|
324
469
|
createInfiniteReadController,
|
|
325
|
-
createSelectorProxy as
|
|
326
|
-
resolvePath as
|
|
470
|
+
createSelectorProxy as createSelectorProxy4,
|
|
471
|
+
resolvePath as resolvePath4,
|
|
327
472
|
resolveTags as resolveTags3
|
|
328
473
|
} from "@spoosh/core";
|
|
329
474
|
function createUseInfiniteRead(options) {
|
|
@@ -339,12 +484,12 @@ function createUseInfiniteRead(options) {
|
|
|
339
484
|
prevPageRequest,
|
|
340
485
|
...pluginOpts
|
|
341
486
|
} = readOptions;
|
|
342
|
-
const hookId =
|
|
343
|
-
const selectorResultRef =
|
|
487
|
+
const hookId = useId4();
|
|
488
|
+
const selectorResultRef = useRef4({
|
|
344
489
|
call: null,
|
|
345
490
|
selector: null
|
|
346
491
|
});
|
|
347
|
-
const selectorProxy =
|
|
492
|
+
const selectorProxy = createSelectorProxy4((result2) => {
|
|
348
493
|
selectorResultRef.current = result2;
|
|
349
494
|
});
|
|
350
495
|
readFn(selectorProxy);
|
|
@@ -367,13 +512,13 @@ function createUseInfiniteRead(options) {
|
|
|
367
512
|
params: void 0,
|
|
368
513
|
body: void 0
|
|
369
514
|
};
|
|
370
|
-
const resolvedPath =
|
|
515
|
+
const resolvedPath = resolvePath4(pathSegments, requestOptions?.params);
|
|
371
516
|
const resolvedTags = resolveTags3({ tags }, resolvedPath);
|
|
372
|
-
const canFetchNextRef =
|
|
373
|
-
const canFetchPrevRef =
|
|
374
|
-
const nextPageRequestRef =
|
|
375
|
-
const prevPageRequestRef =
|
|
376
|
-
const mergerRef =
|
|
517
|
+
const canFetchNextRef = useRef4(canFetchNext);
|
|
518
|
+
const canFetchPrevRef = useRef4(canFetchPrev);
|
|
519
|
+
const nextPageRequestRef = useRef4(nextPageRequest);
|
|
520
|
+
const prevPageRequestRef = useRef4(prevPageRequest);
|
|
521
|
+
const mergerRef = useRef4(merger);
|
|
377
522
|
canFetchNextRef.current = canFetchNext;
|
|
378
523
|
canFetchPrevRef.current = canFetchPrev;
|
|
379
524
|
nextPageRequestRef.current = nextPageRequest;
|
|
@@ -384,7 +529,7 @@ function createUseInfiniteRead(options) {
|
|
|
384
529
|
method: capturedCall.method,
|
|
385
530
|
options: baseOptionsForKey
|
|
386
531
|
});
|
|
387
|
-
const controllerRef =
|
|
532
|
+
const controllerRef = useRef4(null);
|
|
388
533
|
if (!controllerRef.current || controllerRef.current.queryKey !== queryKey) {
|
|
389
534
|
controllerRef.current = {
|
|
390
535
|
controller: createInfiniteReadController({
|
|
@@ -420,12 +565,12 @@ function createUseInfiniteRead(options) {
|
|
|
420
565
|
}
|
|
421
566
|
const controller = controllerRef.current.controller;
|
|
422
567
|
controller.setPluginOptions(pluginOpts);
|
|
423
|
-
const state =
|
|
568
|
+
const state = useSyncExternalStore4(
|
|
424
569
|
controller.subscribe,
|
|
425
570
|
controller.getState,
|
|
426
571
|
controller.getState
|
|
427
572
|
);
|
|
428
|
-
const [isPending, setIsPending] =
|
|
573
|
+
const [isPending, setIsPending] = useState4(() => {
|
|
429
574
|
return enabled && state.data === void 0;
|
|
430
575
|
});
|
|
431
576
|
const fetchingDirection = controller.getFetchingDirection();
|
|
@@ -434,10 +579,11 @@ function createUseInfiniteRead(options) {
|
|
|
434
579
|
const fetchingPrev = fetchingDirection === "prev";
|
|
435
580
|
const hasData = state.data !== void 0;
|
|
436
581
|
const loading = (isPending || fetching) && !hasData;
|
|
437
|
-
const lifecycleRef =
|
|
582
|
+
const lifecycleRef = useRef4({
|
|
438
583
|
initialized: false,
|
|
439
584
|
prevContext: null
|
|
440
585
|
});
|
|
586
|
+
const tagsKey = JSON.stringify(tags);
|
|
441
587
|
useEffect2(() => {
|
|
442
588
|
return () => {
|
|
443
589
|
controllerRef.current?.controller.unmount();
|
|
@@ -462,7 +608,7 @@ function createUseInfiniteRead(options) {
|
|
|
462
608
|
return () => {
|
|
463
609
|
unsubInvalidate();
|
|
464
610
|
};
|
|
465
|
-
}, []);
|
|
611
|
+
}, [tagsKey]);
|
|
466
612
|
useEffect2(() => {
|
|
467
613
|
if (!lifecycleRef.current.initialized) return;
|
|
468
614
|
if (enabled) {
|
|
@@ -493,7 +639,7 @@ function createUseInfiniteRead(options) {
|
|
|
493
639
|
canFetchPrev: state.canFetchPrev,
|
|
494
640
|
fetchNext: controller.fetchNext,
|
|
495
641
|
fetchPrev: controller.fetchPrev,
|
|
496
|
-
|
|
642
|
+
trigger: controller.refetch,
|
|
497
643
|
abort: controller.abort,
|
|
498
644
|
error: state.error
|
|
499
645
|
};
|
|
@@ -516,6 +662,12 @@ function createReactSpoosh(instance) {
|
|
|
516
662
|
eventEmitter,
|
|
517
663
|
pluginExecutor
|
|
518
664
|
});
|
|
665
|
+
const useLazyRead = createUseLazyRead({
|
|
666
|
+
api,
|
|
667
|
+
stateManager,
|
|
668
|
+
eventEmitter,
|
|
669
|
+
pluginExecutor
|
|
670
|
+
});
|
|
519
671
|
const useInfiniteRead = createUseInfiniteRead({
|
|
520
672
|
api,
|
|
521
673
|
stateManager,
|
|
@@ -541,6 +693,7 @@ function createReactSpoosh(instance) {
|
|
|
541
693
|
return {
|
|
542
694
|
useRead,
|
|
543
695
|
useWrite,
|
|
696
|
+
useLazyRead,
|
|
544
697
|
useInfiniteRead,
|
|
545
698
|
...instanceApis
|
|
546
699
|
};
|
|
@@ -548,6 +701,7 @@ function createReactSpoosh(instance) {
|
|
|
548
701
|
export {
|
|
549
702
|
createReactSpoosh,
|
|
550
703
|
createUseInfiniteRead,
|
|
704
|
+
createUseLazyRead,
|
|
551
705
|
createUseRead,
|
|
552
706
|
createUseWrite
|
|
553
707
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spoosh/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React hooks for Spoosh API client",
|
|
6
6
|
"keywords": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"react": "^18 || ^19"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@spoosh/core": "0.
|
|
41
|
+
"@spoosh/core": "0.9.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"dev": "tsup --watch",
|