@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/dist/index.mjs CHANGED
@@ -53,10 +53,11 @@ function createUseRead(options) {
53
53
  initialized: false,
54
54
  prevContext: null
55
55
  });
56
- if (controllerRef.current && controllerRef.current.queryKey !== queryKey) {
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 || controllerRef.current.queryKey !== queryKey) {
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 state = useSyncExternalStore(
81
- controller.subscribe,
82
- controller.getState,
83
- controller.getState
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 response = await controller.execute(void 0, { force });
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
- return executeWithTracking(true);
160
- }, [executeWithTracking]);
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 useRef4,
403
+ useRef as useRef3,
463
404
  useEffect as useEffect2,
464
- useSyncExternalStore as useSyncExternalStore4,
465
- useId as useId4,
466
- useState as useState4
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 createSelectorProxy4,
471
- resolvePath as resolvePath4,
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 = useId4();
488
- const selectorResultRef = useRef4({
428
+ const hookId = useId3();
429
+ const selectorResultRef = useRef3({
489
430
  call: null,
490
431
  selector: null
491
432
  });
492
- const selectorProxy = createSelectorProxy4((result2) => {
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 = resolvePath4(pathSegments, requestOptions?.params);
456
+ const resolvedPath = resolvePath3(pathSegments, requestOptions?.params);
516
457
  const resolvedTags = resolveTags3({ tags }, resolvedPath);
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);
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 = useRef4(null);
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 = useSyncExternalStore4(
509
+ const state = useSyncExternalStore3(
569
510
  controller.subscribe,
570
511
  controller.getState,
571
512
  controller.getState
572
513
  );
573
- const [isPending, setIsPending] = useState4(() => {
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 = useRef4({
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/react",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "license": "MIT",
5
5
  "description": "React hooks for Spoosh API client",
6
6
  "keywords": [