@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/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
- return function useRead(readFn, readOptions) {
22
- const { enabled = true, tags, ...pluginOpts } = readOptions ?? {};
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((result2) => {
29
- selectorResultRef.current = result2;
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 refetch = useCallback(() => {
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
- const result = {
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
- refetch
187
+ trigger
182
188
  };
183
- return result;
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
- return function useWrite(writeFn) {
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((result2) => {
210
- selectorResultRef.current = result2;
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
- const result = {
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
- return result;
312
- };
314
+ }
315
+ return useWrite;
313
316
  }
314
317
 
315
- // src/useInfiniteRead/index.ts
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 useSyncExternalStore3,
320
- useId as useId3,
321
- useState as useState3
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 createSelectorProxy3,
326
- resolvePath as resolvePath3,
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 = useId3();
343
- const selectorResultRef = useRef3({
487
+ const hookId = useId4();
488
+ const selectorResultRef = useRef4({
344
489
  call: null,
345
490
  selector: null
346
491
  });
347
- const selectorProxy = createSelectorProxy3((result2) => {
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 = resolvePath3(pathSegments, requestOptions?.params);
515
+ const resolvedPath = resolvePath4(pathSegments, requestOptions?.params);
371
516
  const resolvedTags = resolveTags3({ tags }, resolvedPath);
372
- const canFetchNextRef = useRef3(canFetchNext);
373
- const canFetchPrevRef = useRef3(canFetchPrev);
374
- const nextPageRequestRef = useRef3(nextPageRequest);
375
- const prevPageRequestRef = useRef3(prevPageRequest);
376
- const mergerRef = useRef3(merger);
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 = useRef3(null);
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 = useSyncExternalStore3(
568
+ const state = useSyncExternalStore4(
424
569
  controller.subscribe,
425
570
  controller.getState,
426
571
  controller.getState
427
572
  );
428
- const [isPending, setIsPending] = useState3(() => {
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 = useRef3({
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
- refetch: controller.refetch,
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.5.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.7.0"
41
+ "@spoosh/core": "0.9.0"
42
42
  },
43
43
  "scripts": {
44
44
  "dev": "tsup --watch",