@spoosh/react 0.6.0 → 0.7.1

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.js CHANGED
@@ -24,7 +24,6 @@ var src_exports = {};
24
24
  __export(src_exports, {
25
25
  createReactSpoosh: () => createReactSpoosh,
26
26
  createUseInfiniteRead: () => createUseInfiniteRead,
27
- createUseLazyRead: () => createUseLazyRead,
28
27
  createUseRead: () => createUseRead,
29
28
  createUseWrite: () => createUseWrite
30
29
  });
@@ -70,10 +69,11 @@ function createUseRead(options) {
70
69
  initialized: false,
71
70
  prevContext: null
72
71
  });
73
- if (controllerRef.current && controllerRef.current.queryKey !== queryKey) {
72
+ const baseQueryKeyChanged = controllerRef.current && controllerRef.current.baseQueryKey !== queryKey;
73
+ if (baseQueryKeyChanged) {
74
74
  lifecycleRef.current.prevContext = controllerRef.current.controller.getContext();
75
75
  }
76
- if (!controllerRef.current || controllerRef.current.queryKey !== queryKey) {
76
+ if (!controllerRef.current || baseQueryKeyChanged) {
77
77
  const controller2 = (0, import_core.createOperationController)({
78
78
  operationType: "read",
79
79
  path: pathSegments,
@@ -90,29 +90,36 @@ function createUseRead(options) {
90
90
  return method(fetchOpts);
91
91
  }
92
92
  });
93
- controllerRef.current = { controller: controller2, queryKey };
93
+ controllerRef.current = { controller: controller2, queryKey, baseQueryKey: queryKey };
94
94
  }
95
95
  const controller = controllerRef.current.controller;
96
96
  controller.setPluginOptions(pluginOpts);
97
- const state = (0, import_react.useSyncExternalStore)(
98
- controller.subscribe,
99
- controller.getState,
100
- controller.getState
97
+ const subscribe = (0, import_react.useCallback)(
98
+ (callback) => {
99
+ return controller.subscribe(callback);
100
+ },
101
+ [controller]
101
102
  );
103
+ const getSnapshot = (0, import_react.useCallback)(() => {
104
+ return controller.getState();
105
+ }, [controller]);
106
+ const state = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
102
107
  const [requestState, setRequestState] = (0, import_react.useState)(() => {
103
108
  const cachedEntry = stateManager.getCache(queryKey);
104
109
  const hasCachedData = cachedEntry?.state?.data !== void 0;
105
110
  return { isPending: enabled && !hasCachedData, error: void 0 };
106
111
  });
112
+ const [, forceUpdate] = (0, import_react.useState)(0);
107
113
  const abortRef = (0, import_react.useRef)(controller.abort);
108
114
  abortRef.current = controller.abort;
109
115
  const pluginOptsKey = JSON.stringify(pluginOpts);
110
116
  const tagsKey = JSON.stringify(tags);
111
117
  const executeWithTracking = (0, import_react.useCallback)(
112
- async (force = false) => {
118
+ async (force = false, overrideOptions) => {
113
119
  setRequestState((prev) => ({ ...prev, isPending: true }));
114
120
  try {
115
- const response = await controller.execute(void 0, { force });
121
+ const execOptions = overrideOptions ? { ...capturedCall.options ?? {}, ...overrideOptions } : void 0;
122
+ const response = await controller.execute(execOptions, { force });
116
123
  if (response.error) {
117
124
  setRequestState({ isPending: false, error: response.error });
118
125
  } else {
@@ -121,10 +128,10 @@ function createUseRead(options) {
121
128
  return response;
122
129
  } catch (err) {
123
130
  setRequestState({ isPending: false, error: err });
124
- throw err;
131
+ return { error: err };
125
132
  }
126
133
  },
127
- [controller]
134
+ [controller, capturedCall.options]
128
135
  );
129
136
  (0, import_react.useEffect)(() => {
130
137
  return () => {
@@ -159,9 +166,13 @@ function createUseRead(options) {
159
166
  }
160
167
  }
161
168
  );
169
+ const unsubRefetchAll = eventEmitter.on("refetchAll", () => {
170
+ executeWithTracking(true);
171
+ });
162
172
  return () => {
163
173
  unsubRefetch();
164
174
  unsubInvalidate();
175
+ unsubRefetchAll();
165
176
  };
166
177
  }, [queryKey, enabled, tagsKey]);
167
178
  (0, import_react.useEffect)(() => {
@@ -172,9 +183,84 @@ function createUseRead(options) {
172
183
  const abort = (0, import_react.useCallback)(() => {
173
184
  abortRef.current();
174
185
  }, []);
175
- const trigger = (0, import_react.useCallback)(() => {
176
- return executeWithTracking(true);
177
- }, [executeWithTracking]);
186
+ const trigger = (0, import_react.useCallback)(
187
+ async (triggerOptions) => {
188
+ const { force = false, ...overrideOptions } = triggerOptions ?? {};
189
+ const hasOverrides = Object.keys(overrideOptions).length > 0;
190
+ if (!hasOverrides) {
191
+ return executeWithTracking(force, void 0);
192
+ }
193
+ const mergedOptions = {
194
+ ...capturedCall.options ?? {},
195
+ ...overrideOptions
196
+ };
197
+ const newQueryKey = stateManager.createQueryKey({
198
+ path: pathSegments,
199
+ method: capturedCall.method,
200
+ options: mergedOptions
201
+ });
202
+ if (newQueryKey === controllerRef.current?.queryKey) {
203
+ return executeWithTracking(force, overrideOptions);
204
+ }
205
+ const params = mergedOptions?.params;
206
+ const newResolvedPath = (0, import_core.resolvePath)(pathSegments, params);
207
+ const newResolvedTags = (0, import_core.resolveTags)({ tags }, newResolvedPath);
208
+ const newController = (0, import_core.createOperationController)({
209
+ operationType: "read",
210
+ path: pathSegments,
211
+ method: capturedCall.method,
212
+ tags: newResolvedTags,
213
+ requestOptions: mergedOptions,
214
+ stateManager,
215
+ eventEmitter,
216
+ pluginExecutor,
217
+ hookId,
218
+ fetchFn: async (fetchOpts) => {
219
+ const pathMethods = api(capturedCall.path);
220
+ const method = pathMethods[capturedCall.method];
221
+ return method(fetchOpts);
222
+ }
223
+ });
224
+ newController.setPluginOptions(pluginOpts);
225
+ const currentBaseQueryKey = controllerRef.current?.baseQueryKey ?? queryKey;
226
+ controllerRef.current = {
227
+ controller: newController,
228
+ queryKey: newQueryKey,
229
+ baseQueryKey: currentBaseQueryKey
230
+ };
231
+ forceUpdate((n) => n + 1);
232
+ newController.mount();
233
+ setRequestState((prev) => ({ ...prev, isPending: true }));
234
+ try {
235
+ const response = await newController.execute(mergedOptions, {
236
+ force
237
+ });
238
+ if (response.error) {
239
+ setRequestState({ isPending: false, error: response.error });
240
+ } else {
241
+ setRequestState({ isPending: false, error: void 0 });
242
+ }
243
+ return response;
244
+ } catch (err) {
245
+ setRequestState({ isPending: false, error: err });
246
+ return { error: err };
247
+ }
248
+ },
249
+ [
250
+ executeWithTracking,
251
+ capturedCall.options,
252
+ capturedCall.method,
253
+ capturedCall.path,
254
+ pathSegments,
255
+ tags,
256
+ stateManager,
257
+ eventEmitter,
258
+ pluginExecutor,
259
+ hookId,
260
+ pluginOpts,
261
+ api
262
+ ]
263
+ );
178
264
  const entry = stateManager.getCache(queryKey);
179
265
  const pluginResultData = entry?.meta ? Object.fromEntries(entry.meta) : {};
180
266
  const opts = capturedCall.options;
@@ -286,7 +372,7 @@ function createUseWrite(options) {
286
372
  return response;
287
373
  } catch (err) {
288
374
  setRequestState({ isPending: false, error: err });
289
- throw err;
375
+ return { error: err };
290
376
  }
291
377
  },
292
378
  [selectedEndpoint.path]
@@ -321,141 +407,9 @@ function createUseWrite(options) {
321
407
  return useWrite;
322
408
  }
323
409
 
324
- // src/useLazyRead/index.ts
410
+ // src/useInfiniteRead/index.ts
325
411
  var import_react3 = require("react");
326
412
  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");
459
413
  function createUseInfiniteRead(options) {
460
414
  const { api, stateManager, eventEmitter, pluginExecutor } = options;
461
415
  return function useInfiniteRead(readFn, readOptions) {
@@ -469,12 +423,12 @@ function createUseInfiniteRead(options) {
469
423
  prevPageRequest,
470
424
  ...pluginOpts
471
425
  } = readOptions;
472
- const hookId = (0, import_react4.useId)();
473
- const selectorResultRef = (0, import_react4.useRef)({
426
+ const hookId = (0, import_react3.useId)();
427
+ const selectorResultRef = (0, import_react3.useRef)({
474
428
  call: null,
475
429
  selector: null
476
430
  });
477
- const selectorProxy = (0, import_core4.createSelectorProxy)((result2) => {
431
+ const selectorProxy = (0, import_core3.createSelectorProxy)((result2) => {
478
432
  selectorResultRef.current = result2;
479
433
  });
480
434
  readFn(selectorProxy);
@@ -497,13 +451,13 @@ function createUseInfiniteRead(options) {
497
451
  params: void 0,
498
452
  body: void 0
499
453
  };
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);
454
+ const resolvedPath = (0, import_core3.resolvePath)(pathSegments, requestOptions?.params);
455
+ const resolvedTags = (0, import_core3.resolveTags)({ tags }, resolvedPath);
456
+ const canFetchNextRef = (0, import_react3.useRef)(canFetchNext);
457
+ const canFetchPrevRef = (0, import_react3.useRef)(canFetchPrev);
458
+ const nextPageRequestRef = (0, import_react3.useRef)(nextPageRequest);
459
+ const prevPageRequestRef = (0, import_react3.useRef)(prevPageRequest);
460
+ const mergerRef = (0, import_react3.useRef)(merger);
507
461
  canFetchNextRef.current = canFetchNext;
508
462
  canFetchPrevRef.current = canFetchPrev;
509
463
  nextPageRequestRef.current = nextPageRequest;
@@ -514,10 +468,10 @@ function createUseInfiniteRead(options) {
514
468
  method: capturedCall.method,
515
469
  options: baseOptionsForKey
516
470
  });
517
- const controllerRef = (0, import_react4.useRef)(null);
471
+ const controllerRef = (0, import_react3.useRef)(null);
518
472
  if (!controllerRef.current || controllerRef.current.queryKey !== queryKey) {
519
473
  controllerRef.current = {
520
- controller: (0, import_core4.createInfiniteReadController)({
474
+ controller: (0, import_core3.createInfiniteReadController)({
521
475
  path: pathSegments,
522
476
  method: capturedCall.method,
523
477
  tags: resolvedTags,
@@ -550,12 +504,12 @@ function createUseInfiniteRead(options) {
550
504
  }
551
505
  const controller = controllerRef.current.controller;
552
506
  controller.setPluginOptions(pluginOpts);
553
- const state = (0, import_react4.useSyncExternalStore)(
507
+ const state = (0, import_react3.useSyncExternalStore)(
554
508
  controller.subscribe,
555
509
  controller.getState,
556
510
  controller.getState
557
511
  );
558
- const [isPending, setIsPending] = (0, import_react4.useState)(() => {
512
+ const [isPending, setIsPending] = (0, import_react3.useState)(() => {
559
513
  return enabled && state.data === void 0;
560
514
  });
561
515
  const fetchingDirection = controller.getFetchingDirection();
@@ -564,18 +518,18 @@ function createUseInfiniteRead(options) {
564
518
  const fetchingPrev = fetchingDirection === "prev";
565
519
  const hasData = state.data !== void 0;
566
520
  const loading = (isPending || fetching) && !hasData;
567
- const lifecycleRef = (0, import_react4.useRef)({
521
+ const lifecycleRef = (0, import_react3.useRef)({
568
522
  initialized: false,
569
523
  prevContext: null
570
524
  });
571
525
  const tagsKey = JSON.stringify(tags);
572
- (0, import_react4.useEffect)(() => {
526
+ (0, import_react3.useEffect)(() => {
573
527
  return () => {
574
528
  controllerRef.current?.controller.unmount();
575
529
  lifecycleRef.current.initialized = false;
576
530
  };
577
531
  }, []);
578
- (0, import_react4.useEffect)(() => {
532
+ (0, import_react3.useEffect)(() => {
579
533
  controller.mount();
580
534
  lifecycleRef.current.initialized = true;
581
535
  const unsubInvalidate = eventEmitter.on(
@@ -590,11 +544,16 @@ function createUseInfiniteRead(options) {
590
544
  }
591
545
  }
592
546
  );
547
+ const unsubRefetchAll = eventEmitter.on("refetchAll", () => {
548
+ setIsPending(true);
549
+ controller.refetch().finally(() => setIsPending(false));
550
+ });
593
551
  return () => {
594
552
  unsubInvalidate();
553
+ unsubRefetchAll();
595
554
  };
596
555
  }, [tagsKey]);
597
- (0, import_react4.useEffect)(() => {
556
+ (0, import_react3.useEffect)(() => {
598
557
  if (!lifecycleRef.current.initialized) return;
599
558
  if (enabled) {
600
559
  const currentState = controller.getState();
@@ -605,7 +564,7 @@ function createUseInfiniteRead(options) {
605
564
  }
606
565
  }
607
566
  }, [enabled]);
608
- (0, import_react4.useEffect)(() => {
567
+ (0, import_react3.useEffect)(() => {
609
568
  if (!enabled || !lifecycleRef.current.initialized) return;
610
569
  const prevContext = controller.getContext();
611
570
  controller.update(prevContext);
@@ -647,12 +606,6 @@ function createReactSpoosh(instance) {
647
606
  eventEmitter,
648
607
  pluginExecutor
649
608
  });
650
- const useLazyRead = createUseLazyRead({
651
- api,
652
- stateManager,
653
- eventEmitter,
654
- pluginExecutor
655
- });
656
609
  const useInfiniteRead = createUseInfiniteRead({
657
610
  api,
658
611
  stateManager,
@@ -678,7 +631,6 @@ function createReactSpoosh(instance) {
678
631
  return {
679
632
  useRead,
680
633
  useWrite,
681
- useLazyRead,
682
634
  useInfiniteRead,
683
635
  ...instanceApis
684
636
  };