@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.js
CHANGED
|
@@ -24,6 +24,7 @@ var src_exports = {};
|
|
|
24
24
|
__export(src_exports, {
|
|
25
25
|
createReactSpoosh: () => createReactSpoosh,
|
|
26
26
|
createUseInfiniteRead: () => createUseInfiniteRead,
|
|
27
|
+
createUseLazyRead: () => createUseLazyRead,
|
|
27
28
|
createUseRead: () => createUseRead,
|
|
28
29
|
createUseWrite: () => createUseWrite
|
|
29
30
|
});
|
|
@@ -34,15 +35,19 @@ var import_react = require("react");
|
|
|
34
35
|
var import_core = require("@spoosh/core");
|
|
35
36
|
function createUseRead(options) {
|
|
36
37
|
const { api, stateManager, eventEmitter, pluginExecutor } = options;
|
|
37
|
-
|
|
38
|
-
const {
|
|
38
|
+
function useRead(readFn, readOptions) {
|
|
39
|
+
const {
|
|
40
|
+
enabled = true,
|
|
41
|
+
tags,
|
|
42
|
+
...pluginOpts
|
|
43
|
+
} = readOptions ?? {};
|
|
39
44
|
const hookId = (0, import_react.useId)();
|
|
40
45
|
const selectorResultRef = (0, import_react.useRef)({
|
|
41
46
|
call: null,
|
|
42
47
|
selector: null
|
|
43
48
|
});
|
|
44
|
-
const selectorProxy = (0, import_core.createSelectorProxy)((
|
|
45
|
-
selectorResultRef.current =
|
|
49
|
+
const selectorProxy = (0, import_core.createSelectorProxy)((result) => {
|
|
50
|
+
selectorResultRef.current = result;
|
|
46
51
|
});
|
|
47
52
|
readFn(selectorProxy);
|
|
48
53
|
const capturedCall = selectorResultRef.current.call;
|
|
@@ -102,6 +107,7 @@ function createUseRead(options) {
|
|
|
102
107
|
const abortRef = (0, import_react.useRef)(controller.abort);
|
|
103
108
|
abortRef.current = controller.abort;
|
|
104
109
|
const pluginOptsKey = JSON.stringify(pluginOpts);
|
|
110
|
+
const tagsKey = JSON.stringify(tags);
|
|
105
111
|
const executeWithTracking = (0, import_react.useCallback)(
|
|
106
112
|
async (force = false) => {
|
|
107
113
|
setRequestState((prev) => ({ ...prev, isPending: true }));
|
|
@@ -157,7 +163,7 @@ function createUseRead(options) {
|
|
|
157
163
|
unsubRefetch();
|
|
158
164
|
unsubInvalidate();
|
|
159
165
|
};
|
|
160
|
-
}, [queryKey, enabled]);
|
|
166
|
+
}, [queryKey, enabled, tagsKey]);
|
|
161
167
|
(0, import_react.useEffect)(() => {
|
|
162
168
|
if (!enabled || !lifecycleRef.current.initialized) return;
|
|
163
169
|
const prevContext = controller.getContext();
|
|
@@ -166,7 +172,7 @@ function createUseRead(options) {
|
|
|
166
172
|
const abort = (0, import_react.useCallback)(() => {
|
|
167
173
|
abortRef.current();
|
|
168
174
|
}, []);
|
|
169
|
-
const
|
|
175
|
+
const trigger = (0, import_react.useCallback)(() => {
|
|
170
176
|
return executeWithTracking(true);
|
|
171
177
|
}, [executeWithTracking]);
|
|
172
178
|
const entry = stateManager.getCache(queryKey);
|
|
@@ -186,7 +192,8 @@ function createUseRead(options) {
|
|
|
186
192
|
const hasData = state.data !== void 0;
|
|
187
193
|
const loading = requestState.isPending && !hasData;
|
|
188
194
|
const fetching = requestState.isPending;
|
|
189
|
-
|
|
195
|
+
return {
|
|
196
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
190
197
|
meta: pluginResultData,
|
|
191
198
|
...inputField,
|
|
192
199
|
data: state.data,
|
|
@@ -194,10 +201,10 @@ function createUseRead(options) {
|
|
|
194
201
|
loading,
|
|
195
202
|
fetching,
|
|
196
203
|
abort,
|
|
197
|
-
|
|
204
|
+
trigger
|
|
198
205
|
};
|
|
199
|
-
|
|
200
|
-
|
|
206
|
+
}
|
|
207
|
+
return useRead;
|
|
201
208
|
}
|
|
202
209
|
|
|
203
210
|
// src/useWrite/index.ts
|
|
@@ -205,14 +212,14 @@ var import_react2 = require("react");
|
|
|
205
212
|
var import_core2 = require("@spoosh/core");
|
|
206
213
|
function createUseWrite(options) {
|
|
207
214
|
const { api, stateManager, pluginExecutor, eventEmitter } = options;
|
|
208
|
-
|
|
215
|
+
function useWrite(writeFn) {
|
|
209
216
|
const hookId = (0, import_react2.useId)();
|
|
210
217
|
const selectorResultRef = (0, import_react2.useRef)({
|
|
211
218
|
call: null,
|
|
212
219
|
selector: null
|
|
213
220
|
});
|
|
214
|
-
const selectorProxy = (0, import_core2.createSelectorProxy)((
|
|
215
|
-
selectorResultRef.current =
|
|
221
|
+
const selectorProxy = (0, import_core2.createSelectorProxy)((result) => {
|
|
222
|
+
selectorResultRef.current = result;
|
|
216
223
|
});
|
|
217
224
|
writeFn(selectorProxy);
|
|
218
225
|
const selectedEndpoint = selectorResultRef.current.selector;
|
|
@@ -256,10 +263,6 @@ function createUseWrite(options) {
|
|
|
256
263
|
);
|
|
257
264
|
const [lastTriggerOptions, setLastTriggerOptions] = (0, import_react2.useState)(void 0);
|
|
258
265
|
const [requestState, setRequestState] = (0, import_react2.useState)({ isPending: false, error: void 0 });
|
|
259
|
-
const reset = (0, import_react2.useCallback)(() => {
|
|
260
|
-
stateManager.deleteCache(queryKey);
|
|
261
|
-
setRequestState({ isPending: false, error: void 0 });
|
|
262
|
-
}, [queryKey]);
|
|
263
266
|
const abort = (0, import_react2.useCallback)(() => {
|
|
264
267
|
controller.abort();
|
|
265
268
|
}, []);
|
|
@@ -303,23 +306,156 @@ function createUseWrite(options) {
|
|
|
303
306
|
}
|
|
304
307
|
const inputField = Object.keys(inputInner).length > 0 ? { input: inputInner } : {};
|
|
305
308
|
const loading = requestState.isPending;
|
|
306
|
-
|
|
309
|
+
return {
|
|
307
310
|
trigger,
|
|
311
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
308
312
|
meta: pluginResultData,
|
|
309
313
|
...inputField,
|
|
310
314
|
data: state.data,
|
|
311
315
|
error: requestState.error ?? state.error,
|
|
312
316
|
loading,
|
|
313
|
-
reset,
|
|
314
317
|
abort
|
|
318
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
315
319
|
};
|
|
316
|
-
|
|
317
|
-
|
|
320
|
+
}
|
|
321
|
+
return useWrite;
|
|
318
322
|
}
|
|
319
323
|
|
|
320
|
-
// src/
|
|
324
|
+
// src/useLazyRead/index.ts
|
|
321
325
|
var import_react3 = require("react");
|
|
322
326
|
var import_core3 = require("@spoosh/core");
|
|
327
|
+
function createUseLazyRead(options) {
|
|
328
|
+
const { api, stateManager, pluginExecutor, eventEmitter } = options;
|
|
329
|
+
function useLazyRead(readFn) {
|
|
330
|
+
const hookId = (0, import_react3.useId)();
|
|
331
|
+
const selectorResultRef = (0, import_react3.useRef)({
|
|
332
|
+
call: null,
|
|
333
|
+
selector: null
|
|
334
|
+
});
|
|
335
|
+
const selectorProxy = (0, import_core3.createSelectorProxy)((result) => {
|
|
336
|
+
selectorResultRef.current = result;
|
|
337
|
+
});
|
|
338
|
+
readFn(selectorProxy);
|
|
339
|
+
const selectedEndpoint = selectorResultRef.current.selector;
|
|
340
|
+
if (!selectedEndpoint) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
'useLazyRead requires selecting an HTTP method (GET). Example: useLazyRead((api) => api("posts").GET)'
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
if (selectedEndpoint.method !== "GET") {
|
|
346
|
+
throw new Error(
|
|
347
|
+
"useLazyRead only supports GET method. Use useWrite for POST, PUT, PATCH, DELETE methods."
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
const pathSegments = selectedEndpoint.path.split("/").filter(Boolean);
|
|
351
|
+
const controllerRef = (0, import_react3.useRef)(null);
|
|
352
|
+
const emptyStateRef = (0, import_react3.useRef)({ data: void 0, error: void 0 });
|
|
353
|
+
const [currentQueryKey, setCurrentQueryKey] = (0, import_react3.useState)(null);
|
|
354
|
+
const [, forceUpdate] = (0, import_react3.useState)(0);
|
|
355
|
+
const getOrCreateController = (0, import_react3.useCallback)(
|
|
356
|
+
(triggerOptions) => {
|
|
357
|
+
const queryKey = stateManager.createQueryKey({
|
|
358
|
+
path: pathSegments,
|
|
359
|
+
method: selectedEndpoint.method,
|
|
360
|
+
options: triggerOptions
|
|
361
|
+
});
|
|
362
|
+
if (controllerRef.current?.queryKey === queryKey) {
|
|
363
|
+
return { controller: controllerRef.current.controller, queryKey };
|
|
364
|
+
}
|
|
365
|
+
const controller2 = (0, import_core3.createOperationController)({
|
|
366
|
+
operationType: "read",
|
|
367
|
+
path: pathSegments,
|
|
368
|
+
method: "GET",
|
|
369
|
+
tags: [],
|
|
370
|
+
stateManager,
|
|
371
|
+
eventEmitter,
|
|
372
|
+
pluginExecutor,
|
|
373
|
+
hookId,
|
|
374
|
+
requestOptions: triggerOptions,
|
|
375
|
+
fetchFn: async (fetchOpts) => {
|
|
376
|
+
const pathMethods = api(selectedEndpoint.path);
|
|
377
|
+
const method = pathMethods[selectedEndpoint.method];
|
|
378
|
+
return method(fetchOpts);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
controllerRef.current = { controller: controller2, queryKey };
|
|
382
|
+
setCurrentQueryKey(queryKey);
|
|
383
|
+
forceUpdate((n) => n + 1);
|
|
384
|
+
return { controller: controller2, queryKey };
|
|
385
|
+
},
|
|
386
|
+
[pathSegments, selectedEndpoint.method, selectedEndpoint.path, hookId]
|
|
387
|
+
);
|
|
388
|
+
const controller = controllerRef.current?.controller;
|
|
389
|
+
const subscribe = (0, import_react3.useCallback)(
|
|
390
|
+
(callback) => {
|
|
391
|
+
if (!controller) return () => {
|
|
392
|
+
};
|
|
393
|
+
return controller.subscribe(callback);
|
|
394
|
+
},
|
|
395
|
+
[controller]
|
|
396
|
+
);
|
|
397
|
+
const getSnapshot = (0, import_react3.useCallback)(() => {
|
|
398
|
+
if (!controller) return emptyStateRef.current;
|
|
399
|
+
return controller.getState();
|
|
400
|
+
}, [controller]);
|
|
401
|
+
const state = (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
402
|
+
const [lastTriggerOptions, setLastTriggerOptions] = (0, import_react3.useState)(void 0);
|
|
403
|
+
const [requestState, setRequestState] = (0, import_react3.useState)({ isPending: false, error: void 0 });
|
|
404
|
+
const abort = (0, import_react3.useCallback)(() => {
|
|
405
|
+
controllerRef.current?.controller.abort();
|
|
406
|
+
}, []);
|
|
407
|
+
const trigger = (0, import_react3.useCallback)(
|
|
408
|
+
async (triggerOptions) => {
|
|
409
|
+
setLastTriggerOptions(triggerOptions);
|
|
410
|
+
setRequestState((prev) => ({ ...prev, isPending: true }));
|
|
411
|
+
const params = triggerOptions?.params;
|
|
412
|
+
(0, import_core3.resolvePath)(pathSegments, params);
|
|
413
|
+
const { controller: ctrl } = getOrCreateController(triggerOptions);
|
|
414
|
+
ctrl.setPluginOptions(triggerOptions);
|
|
415
|
+
try {
|
|
416
|
+
const response = await ctrl.execute(triggerOptions);
|
|
417
|
+
if (response.error) {
|
|
418
|
+
setRequestState({ isPending: false, error: response.error });
|
|
419
|
+
} else {
|
|
420
|
+
setRequestState({ isPending: false, error: void 0 });
|
|
421
|
+
}
|
|
422
|
+
return response;
|
|
423
|
+
} catch (err) {
|
|
424
|
+
setRequestState({ isPending: false, error: err });
|
|
425
|
+
throw err;
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
[pathSegments, getOrCreateController]
|
|
429
|
+
);
|
|
430
|
+
const opts = lastTriggerOptions;
|
|
431
|
+
const inputInner = {};
|
|
432
|
+
if (opts?.query !== void 0) {
|
|
433
|
+
inputInner.query = opts.query;
|
|
434
|
+
}
|
|
435
|
+
if (opts?.body !== void 0) {
|
|
436
|
+
inputInner.body = opts.body;
|
|
437
|
+
}
|
|
438
|
+
if (opts?.params !== void 0) {
|
|
439
|
+
inputInner.params = opts.params;
|
|
440
|
+
}
|
|
441
|
+
const inputField = Object.keys(inputInner).length > 0 ? { input: inputInner } : {};
|
|
442
|
+
const loading = requestState.isPending;
|
|
443
|
+
return {
|
|
444
|
+
trigger,
|
|
445
|
+
...inputField,
|
|
446
|
+
data: state.data,
|
|
447
|
+
error: requestState.error ?? state.error,
|
|
448
|
+
loading,
|
|
449
|
+
abort
|
|
450
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
return useLazyRead;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/useInfiniteRead/index.ts
|
|
457
|
+
var import_react4 = require("react");
|
|
458
|
+
var import_core4 = require("@spoosh/core");
|
|
323
459
|
function createUseInfiniteRead(options) {
|
|
324
460
|
const { api, stateManager, eventEmitter, pluginExecutor } = options;
|
|
325
461
|
return function useInfiniteRead(readFn, readOptions) {
|
|
@@ -333,12 +469,12 @@ function createUseInfiniteRead(options) {
|
|
|
333
469
|
prevPageRequest,
|
|
334
470
|
...pluginOpts
|
|
335
471
|
} = readOptions;
|
|
336
|
-
const hookId = (0,
|
|
337
|
-
const selectorResultRef = (0,
|
|
472
|
+
const hookId = (0, import_react4.useId)();
|
|
473
|
+
const selectorResultRef = (0, import_react4.useRef)({
|
|
338
474
|
call: null,
|
|
339
475
|
selector: null
|
|
340
476
|
});
|
|
341
|
-
const selectorProxy = (0,
|
|
477
|
+
const selectorProxy = (0, import_core4.createSelectorProxy)((result2) => {
|
|
342
478
|
selectorResultRef.current = result2;
|
|
343
479
|
});
|
|
344
480
|
readFn(selectorProxy);
|
|
@@ -361,13 +497,13 @@ function createUseInfiniteRead(options) {
|
|
|
361
497
|
params: void 0,
|
|
362
498
|
body: void 0
|
|
363
499
|
};
|
|
364
|
-
const resolvedPath = (0,
|
|
365
|
-
const resolvedTags = (0,
|
|
366
|
-
const canFetchNextRef = (0,
|
|
367
|
-
const canFetchPrevRef = (0,
|
|
368
|
-
const nextPageRequestRef = (0,
|
|
369
|
-
const prevPageRequestRef = (0,
|
|
370
|
-
const mergerRef = (0,
|
|
500
|
+
const resolvedPath = (0, import_core4.resolvePath)(pathSegments, requestOptions?.params);
|
|
501
|
+
const resolvedTags = (0, import_core4.resolveTags)({ tags }, resolvedPath);
|
|
502
|
+
const canFetchNextRef = (0, import_react4.useRef)(canFetchNext);
|
|
503
|
+
const canFetchPrevRef = (0, import_react4.useRef)(canFetchPrev);
|
|
504
|
+
const nextPageRequestRef = (0, import_react4.useRef)(nextPageRequest);
|
|
505
|
+
const prevPageRequestRef = (0, import_react4.useRef)(prevPageRequest);
|
|
506
|
+
const mergerRef = (0, import_react4.useRef)(merger);
|
|
371
507
|
canFetchNextRef.current = canFetchNext;
|
|
372
508
|
canFetchPrevRef.current = canFetchPrev;
|
|
373
509
|
nextPageRequestRef.current = nextPageRequest;
|
|
@@ -378,10 +514,10 @@ function createUseInfiniteRead(options) {
|
|
|
378
514
|
method: capturedCall.method,
|
|
379
515
|
options: baseOptionsForKey
|
|
380
516
|
});
|
|
381
|
-
const controllerRef = (0,
|
|
517
|
+
const controllerRef = (0, import_react4.useRef)(null);
|
|
382
518
|
if (!controllerRef.current || controllerRef.current.queryKey !== queryKey) {
|
|
383
519
|
controllerRef.current = {
|
|
384
|
-
controller: (0,
|
|
520
|
+
controller: (0, import_core4.createInfiniteReadController)({
|
|
385
521
|
path: pathSegments,
|
|
386
522
|
method: capturedCall.method,
|
|
387
523
|
tags: resolvedTags,
|
|
@@ -414,12 +550,12 @@ function createUseInfiniteRead(options) {
|
|
|
414
550
|
}
|
|
415
551
|
const controller = controllerRef.current.controller;
|
|
416
552
|
controller.setPluginOptions(pluginOpts);
|
|
417
|
-
const state = (0,
|
|
553
|
+
const state = (0, import_react4.useSyncExternalStore)(
|
|
418
554
|
controller.subscribe,
|
|
419
555
|
controller.getState,
|
|
420
556
|
controller.getState
|
|
421
557
|
);
|
|
422
|
-
const [isPending, setIsPending] = (0,
|
|
558
|
+
const [isPending, setIsPending] = (0, import_react4.useState)(() => {
|
|
423
559
|
return enabled && state.data === void 0;
|
|
424
560
|
});
|
|
425
561
|
const fetchingDirection = controller.getFetchingDirection();
|
|
@@ -428,17 +564,18 @@ function createUseInfiniteRead(options) {
|
|
|
428
564
|
const fetchingPrev = fetchingDirection === "prev";
|
|
429
565
|
const hasData = state.data !== void 0;
|
|
430
566
|
const loading = (isPending || fetching) && !hasData;
|
|
431
|
-
const lifecycleRef = (0,
|
|
567
|
+
const lifecycleRef = (0, import_react4.useRef)({
|
|
432
568
|
initialized: false,
|
|
433
569
|
prevContext: null
|
|
434
570
|
});
|
|
435
|
-
|
|
571
|
+
const tagsKey = JSON.stringify(tags);
|
|
572
|
+
(0, import_react4.useEffect)(() => {
|
|
436
573
|
return () => {
|
|
437
574
|
controllerRef.current?.controller.unmount();
|
|
438
575
|
lifecycleRef.current.initialized = false;
|
|
439
576
|
};
|
|
440
577
|
}, []);
|
|
441
|
-
(0,
|
|
578
|
+
(0, import_react4.useEffect)(() => {
|
|
442
579
|
controller.mount();
|
|
443
580
|
lifecycleRef.current.initialized = true;
|
|
444
581
|
const unsubInvalidate = eventEmitter.on(
|
|
@@ -456,8 +593,8 @@ function createUseInfiniteRead(options) {
|
|
|
456
593
|
return () => {
|
|
457
594
|
unsubInvalidate();
|
|
458
595
|
};
|
|
459
|
-
}, []);
|
|
460
|
-
(0,
|
|
596
|
+
}, [tagsKey]);
|
|
597
|
+
(0, import_react4.useEffect)(() => {
|
|
461
598
|
if (!lifecycleRef.current.initialized) return;
|
|
462
599
|
if (enabled) {
|
|
463
600
|
const currentState = controller.getState();
|
|
@@ -468,7 +605,7 @@ function createUseInfiniteRead(options) {
|
|
|
468
605
|
}
|
|
469
606
|
}
|
|
470
607
|
}, [enabled]);
|
|
471
|
-
(0,
|
|
608
|
+
(0, import_react4.useEffect)(() => {
|
|
472
609
|
if (!enabled || !lifecycleRef.current.initialized) return;
|
|
473
610
|
const prevContext = controller.getContext();
|
|
474
611
|
controller.update(prevContext);
|
|
@@ -487,7 +624,7 @@ function createUseInfiniteRead(options) {
|
|
|
487
624
|
canFetchPrev: state.canFetchPrev,
|
|
488
625
|
fetchNext: controller.fetchNext,
|
|
489
626
|
fetchPrev: controller.fetchPrev,
|
|
490
|
-
|
|
627
|
+
trigger: controller.refetch,
|
|
491
628
|
abort: controller.abort,
|
|
492
629
|
error: state.error
|
|
493
630
|
};
|
|
@@ -510,6 +647,12 @@ function createReactSpoosh(instance) {
|
|
|
510
647
|
eventEmitter,
|
|
511
648
|
pluginExecutor
|
|
512
649
|
});
|
|
650
|
+
const useLazyRead = createUseLazyRead({
|
|
651
|
+
api,
|
|
652
|
+
stateManager,
|
|
653
|
+
eventEmitter,
|
|
654
|
+
pluginExecutor
|
|
655
|
+
});
|
|
513
656
|
const useInfiniteRead = createUseInfiniteRead({
|
|
514
657
|
api,
|
|
515
658
|
stateManager,
|
|
@@ -535,6 +678,7 @@ function createReactSpoosh(instance) {
|
|
|
535
678
|
return {
|
|
536
679
|
useRead,
|
|
537
680
|
useWrite,
|
|
681
|
+
useLazyRead,
|
|
538
682
|
useInfiniteRead,
|
|
539
683
|
...instanceApis
|
|
540
684
|
};
|