@spoosh/react 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +222 -0
- package/dist/index.d.mts +411 -0
- package/dist/index.d.ts +411 -0
- package/dist/index.js +549 -0
- package/dist/index.mjs +555 -0
- package/package.json +49 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/useRead/index.ts
|
|
5
|
+
import {
|
|
6
|
+
useSyncExternalStore,
|
|
7
|
+
useRef,
|
|
8
|
+
useEffect,
|
|
9
|
+
useCallback,
|
|
10
|
+
useId,
|
|
11
|
+
useState
|
|
12
|
+
} from "react";
|
|
13
|
+
import {
|
|
14
|
+
createOperationController,
|
|
15
|
+
createSelectorProxy,
|
|
16
|
+
resolvePath,
|
|
17
|
+
resolveTags
|
|
18
|
+
} from "@spoosh/core";
|
|
19
|
+
function createUseRead(options) {
|
|
20
|
+
const { api, stateManager, eventEmitter, pluginExecutor } = options;
|
|
21
|
+
return function useRead(readFn, readOptions) {
|
|
22
|
+
const {
|
|
23
|
+
enabled = true,
|
|
24
|
+
tags,
|
|
25
|
+
additionalTags,
|
|
26
|
+
...pluginOpts
|
|
27
|
+
} = readOptions ?? {};
|
|
28
|
+
const hookId = useId();
|
|
29
|
+
const selectorResultRef = useRef({
|
|
30
|
+
call: null,
|
|
31
|
+
selector: null
|
|
32
|
+
});
|
|
33
|
+
const selectorProxy = createSelectorProxy((result2) => {
|
|
34
|
+
selectorResultRef.current = result2;
|
|
35
|
+
});
|
|
36
|
+
readFn(selectorProxy);
|
|
37
|
+
const capturedCall = selectorResultRef.current.call;
|
|
38
|
+
if (!capturedCall) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
"useRead requires calling an HTTP method ($get). Example: useRead((api) => api.posts.$get())"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const requestOptions = capturedCall.options;
|
|
44
|
+
const resolvedPath = resolvePath(capturedCall.path, requestOptions?.params);
|
|
45
|
+
const resolvedTags = resolveTags({ tags, additionalTags }, resolvedPath);
|
|
46
|
+
const queryKey = stateManager.createQueryKey({
|
|
47
|
+
path: capturedCall.path,
|
|
48
|
+
method: capturedCall.method,
|
|
49
|
+
options: capturedCall.options
|
|
50
|
+
});
|
|
51
|
+
const controllerRef = useRef(null);
|
|
52
|
+
const lifecycleRef = useRef({
|
|
53
|
+
initialized: false,
|
|
54
|
+
prevContext: null
|
|
55
|
+
});
|
|
56
|
+
if (controllerRef.current && controllerRef.current.queryKey !== queryKey) {
|
|
57
|
+
lifecycleRef.current.prevContext = controllerRef.current.controller.getContext();
|
|
58
|
+
}
|
|
59
|
+
if (!controllerRef.current || controllerRef.current.queryKey !== queryKey) {
|
|
60
|
+
const controller2 = createOperationController({
|
|
61
|
+
operationType: "read",
|
|
62
|
+
path: capturedCall.path,
|
|
63
|
+
method: capturedCall.method,
|
|
64
|
+
tags: resolvedTags,
|
|
65
|
+
requestOptions: capturedCall.options,
|
|
66
|
+
stateManager,
|
|
67
|
+
eventEmitter,
|
|
68
|
+
pluginExecutor,
|
|
69
|
+
hookId,
|
|
70
|
+
fetchFn: async (fetchOpts) => {
|
|
71
|
+
let current = api;
|
|
72
|
+
for (const segment of resolvedPath) {
|
|
73
|
+
current = current[segment];
|
|
74
|
+
}
|
|
75
|
+
const method = current[capturedCall.method];
|
|
76
|
+
return method(fetchOpts);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
controllerRef.current = { controller: controller2, queryKey };
|
|
80
|
+
}
|
|
81
|
+
const controller = controllerRef.current.controller;
|
|
82
|
+
controller.setPluginOptions(pluginOpts);
|
|
83
|
+
const state = useSyncExternalStore(
|
|
84
|
+
controller.subscribe,
|
|
85
|
+
controller.getState,
|
|
86
|
+
controller.getState
|
|
87
|
+
);
|
|
88
|
+
const [requestState, setRequestState] = useState({ isPending: false, error: void 0 });
|
|
89
|
+
const abortRef = useRef(controller.abort);
|
|
90
|
+
abortRef.current = controller.abort;
|
|
91
|
+
const pluginOptsKey = JSON.stringify(pluginOpts);
|
|
92
|
+
const executeWithTracking = useCallback(
|
|
93
|
+
async (force = false) => {
|
|
94
|
+
setRequestState((prev) => ({ ...prev, isPending: true }));
|
|
95
|
+
try {
|
|
96
|
+
const response = await controller.execute(void 0, { force });
|
|
97
|
+
if (response.error) {
|
|
98
|
+
setRequestState({ isPending: false, error: response.error });
|
|
99
|
+
} else {
|
|
100
|
+
setRequestState({ isPending: false, error: void 0 });
|
|
101
|
+
}
|
|
102
|
+
return response;
|
|
103
|
+
} catch (err) {
|
|
104
|
+
setRequestState({ isPending: false, error: err });
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
[controller]
|
|
109
|
+
);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
return () => {
|
|
112
|
+
controllerRef.current?.controller.unmount();
|
|
113
|
+
lifecycleRef.current.initialized = false;
|
|
114
|
+
};
|
|
115
|
+
}, []);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!enabled) return;
|
|
118
|
+
const { initialized, prevContext } = lifecycleRef.current;
|
|
119
|
+
if (!initialized) {
|
|
120
|
+
controller.mount();
|
|
121
|
+
lifecycleRef.current.initialized = true;
|
|
122
|
+
} else if (prevContext) {
|
|
123
|
+
controller.update(prevContext);
|
|
124
|
+
lifecycleRef.current.prevContext = null;
|
|
125
|
+
}
|
|
126
|
+
executeWithTracking(false);
|
|
127
|
+
const unsubRefetch = eventEmitter.on("refetch", (event) => {
|
|
128
|
+
if (event.queryKey === queryKey) {
|
|
129
|
+
executeWithTracking(true);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
const unsubInvalidate = eventEmitter.on(
|
|
133
|
+
"invalidate",
|
|
134
|
+
(invalidatedTags) => {
|
|
135
|
+
const hasMatch = invalidatedTags.some(
|
|
136
|
+
(tag) => resolvedTags.includes(tag)
|
|
137
|
+
);
|
|
138
|
+
if (hasMatch) {
|
|
139
|
+
executeWithTracking(true);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
return () => {
|
|
144
|
+
unsubRefetch();
|
|
145
|
+
unsubInvalidate();
|
|
146
|
+
};
|
|
147
|
+
}, [queryKey, enabled]);
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!enabled || !lifecycleRef.current.initialized) return;
|
|
150
|
+
const prevContext = controller.getContext();
|
|
151
|
+
controller.update(prevContext);
|
|
152
|
+
}, [pluginOptsKey]);
|
|
153
|
+
const abort = useCallback(() => {
|
|
154
|
+
abortRef.current();
|
|
155
|
+
}, []);
|
|
156
|
+
const refetch = useCallback(() => {
|
|
157
|
+
return executeWithTracking(true);
|
|
158
|
+
}, [executeWithTracking]);
|
|
159
|
+
const entry = stateManager.getCache(queryKey);
|
|
160
|
+
const pluginResultData = entry?.pluginResult ? Object.fromEntries(entry.pluginResult) : {};
|
|
161
|
+
const opts = capturedCall.options;
|
|
162
|
+
const inputInner = {};
|
|
163
|
+
if (opts?.query !== void 0) {
|
|
164
|
+
inputInner.query = opts.query;
|
|
165
|
+
}
|
|
166
|
+
if (opts?.body !== void 0) {
|
|
167
|
+
inputInner.body = opts.body;
|
|
168
|
+
}
|
|
169
|
+
if (opts?.formData !== void 0) {
|
|
170
|
+
inputInner.formData = opts.formData;
|
|
171
|
+
}
|
|
172
|
+
if (opts?.params !== void 0) {
|
|
173
|
+
inputInner.params = opts.params;
|
|
174
|
+
}
|
|
175
|
+
const inputField = Object.keys(inputInner).length > 0 ? { input: inputInner } : {};
|
|
176
|
+
const hasData = state.data !== void 0;
|
|
177
|
+
const loading = requestState.isPending && !hasData;
|
|
178
|
+
const fetching = requestState.isPending;
|
|
179
|
+
const result = {
|
|
180
|
+
...pluginResultData,
|
|
181
|
+
...inputField,
|
|
182
|
+
data: state.data,
|
|
183
|
+
error: requestState.error ?? state.error,
|
|
184
|
+
loading,
|
|
185
|
+
fetching,
|
|
186
|
+
abort,
|
|
187
|
+
refetch
|
|
188
|
+
};
|
|
189
|
+
return result;
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/useWrite/index.ts
|
|
194
|
+
import {
|
|
195
|
+
useSyncExternalStore as useSyncExternalStore2,
|
|
196
|
+
useRef as useRef2,
|
|
197
|
+
useCallback as useCallback2,
|
|
198
|
+
useState as useState2,
|
|
199
|
+
useId as useId2
|
|
200
|
+
} from "react";
|
|
201
|
+
import {
|
|
202
|
+
createOperationController as createOperationController2,
|
|
203
|
+
createSelectorProxy as createSelectorProxy2,
|
|
204
|
+
resolvePath as resolvePath2,
|
|
205
|
+
resolveTags as resolveTags2
|
|
206
|
+
} from "@spoosh/core";
|
|
207
|
+
function createUseWrite(options) {
|
|
208
|
+
const { api, stateManager, pluginExecutor, eventEmitter } = options;
|
|
209
|
+
return function useWrite(writeFn) {
|
|
210
|
+
const hookId = useId2();
|
|
211
|
+
const selectorResultRef = useRef2({
|
|
212
|
+
call: null,
|
|
213
|
+
selector: null
|
|
214
|
+
});
|
|
215
|
+
const selectorProxy = createSelectorProxy2((result2) => {
|
|
216
|
+
selectorResultRef.current = result2;
|
|
217
|
+
});
|
|
218
|
+
writeFn(selectorProxy);
|
|
219
|
+
const selectedEndpoint = selectorResultRef.current.selector;
|
|
220
|
+
if (!selectedEndpoint) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
"useWrite requires selecting an HTTP method ($post, $put, $patch, $delete). Example: useWrite((api) => api.posts.$post)"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
const queryKey = stateManager.createQueryKey({
|
|
226
|
+
path: selectedEndpoint.path,
|
|
227
|
+
method: selectedEndpoint.method,
|
|
228
|
+
options: void 0
|
|
229
|
+
});
|
|
230
|
+
const controllerRef = useRef2(null);
|
|
231
|
+
if (!controllerRef.current || controllerRef.current.queryKey !== queryKey) {
|
|
232
|
+
controllerRef.current = {
|
|
233
|
+
controller: createOperationController2({
|
|
234
|
+
operationType: "write",
|
|
235
|
+
path: selectedEndpoint.path,
|
|
236
|
+
method: selectedEndpoint.method,
|
|
237
|
+
tags: [],
|
|
238
|
+
stateManager,
|
|
239
|
+
eventEmitter,
|
|
240
|
+
pluginExecutor,
|
|
241
|
+
hookId,
|
|
242
|
+
fetchFn: async (fetchOpts) => {
|
|
243
|
+
const params = fetchOpts?.params;
|
|
244
|
+
const resolvedPath = resolvePath2(selectedEndpoint.path, params);
|
|
245
|
+
let current = api;
|
|
246
|
+
for (const segment of resolvedPath) {
|
|
247
|
+
current = current[segment];
|
|
248
|
+
}
|
|
249
|
+
const method = current[selectedEndpoint.method];
|
|
250
|
+
return method(fetchOpts);
|
|
251
|
+
}
|
|
252
|
+
}),
|
|
253
|
+
queryKey
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const controller = controllerRef.current.controller;
|
|
257
|
+
const state = useSyncExternalStore2(
|
|
258
|
+
controller.subscribe,
|
|
259
|
+
controller.getState,
|
|
260
|
+
controller.getState
|
|
261
|
+
);
|
|
262
|
+
const [lastTriggerOptions, setLastTriggerOptions] = useState2(void 0);
|
|
263
|
+
const [requestState, setRequestState] = useState2({ isPending: false, error: void 0 });
|
|
264
|
+
const reset = useCallback2(() => {
|
|
265
|
+
stateManager.deleteCache(queryKey);
|
|
266
|
+
setRequestState({ isPending: false, error: void 0 });
|
|
267
|
+
}, [queryKey]);
|
|
268
|
+
const abort = useCallback2(() => {
|
|
269
|
+
controller.abort();
|
|
270
|
+
}, []);
|
|
271
|
+
const trigger = useCallback2(
|
|
272
|
+
async (triggerOptions) => {
|
|
273
|
+
setLastTriggerOptions(triggerOptions);
|
|
274
|
+
setRequestState((prev) => ({ ...prev, isPending: true }));
|
|
275
|
+
const params = triggerOptions?.params;
|
|
276
|
+
const resolvedPath = resolvePath2(selectedEndpoint.path, params);
|
|
277
|
+
const tags = resolveTags2(triggerOptions, resolvedPath);
|
|
278
|
+
controller.setPluginOptions({ ...triggerOptions, tags });
|
|
279
|
+
try {
|
|
280
|
+
const response = await controller.execute(triggerOptions, {
|
|
281
|
+
force: true
|
|
282
|
+
});
|
|
283
|
+
if (response.error) {
|
|
284
|
+
setRequestState({ isPending: false, error: response.error });
|
|
285
|
+
} else {
|
|
286
|
+
setRequestState({ isPending: false, error: void 0 });
|
|
287
|
+
}
|
|
288
|
+
return response;
|
|
289
|
+
} catch (err) {
|
|
290
|
+
setRequestState({ isPending: false, error: err });
|
|
291
|
+
throw err;
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
[selectedEndpoint.path]
|
|
295
|
+
);
|
|
296
|
+
const entry = stateManager.getCache(queryKey);
|
|
297
|
+
const pluginResultData = entry?.pluginResult ? Object.fromEntries(entry.pluginResult) : {};
|
|
298
|
+
const opts = lastTriggerOptions;
|
|
299
|
+
const inputInner = {};
|
|
300
|
+
if (opts?.query !== void 0) {
|
|
301
|
+
inputInner.query = opts.query;
|
|
302
|
+
}
|
|
303
|
+
if (opts?.body !== void 0) {
|
|
304
|
+
inputInner.body = opts.body;
|
|
305
|
+
}
|
|
306
|
+
if (opts?.formData !== void 0) {
|
|
307
|
+
inputInner.formData = opts.formData;
|
|
308
|
+
}
|
|
309
|
+
if (opts?.params !== void 0) {
|
|
310
|
+
inputInner.params = opts.params;
|
|
311
|
+
}
|
|
312
|
+
const inputField = Object.keys(inputInner).length > 0 ? { input: inputInner } : {};
|
|
313
|
+
const loading = requestState.isPending;
|
|
314
|
+
const result = {
|
|
315
|
+
trigger,
|
|
316
|
+
...pluginResultData,
|
|
317
|
+
...inputField,
|
|
318
|
+
data: state.data,
|
|
319
|
+
error: requestState.error ?? state.error,
|
|
320
|
+
loading,
|
|
321
|
+
reset,
|
|
322
|
+
abort
|
|
323
|
+
};
|
|
324
|
+
return result;
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/useInfiniteRead/index.ts
|
|
329
|
+
import { useRef as useRef3, useEffect as useEffect2, useSyncExternalStore as useSyncExternalStore3, useId as useId3 } from "react";
|
|
330
|
+
import {
|
|
331
|
+
createInfiniteReadController,
|
|
332
|
+
createSelectorProxy as createSelectorProxy3,
|
|
333
|
+
resolvePath as resolvePath3,
|
|
334
|
+
resolveTags as resolveTags3
|
|
335
|
+
} from "@spoosh/core";
|
|
336
|
+
function createUseInfiniteRead(options) {
|
|
337
|
+
const { api, stateManager, eventEmitter, pluginExecutor } = options;
|
|
338
|
+
return function useInfiniteRead(readFn, readOptions) {
|
|
339
|
+
const {
|
|
340
|
+
enabled = true,
|
|
341
|
+
tags,
|
|
342
|
+
additionalTags,
|
|
343
|
+
canFetchNext,
|
|
344
|
+
nextPageRequest,
|
|
345
|
+
merger,
|
|
346
|
+
canFetchPrev,
|
|
347
|
+
prevPageRequest,
|
|
348
|
+
...pluginOpts
|
|
349
|
+
} = readOptions;
|
|
350
|
+
const hookId = useId3();
|
|
351
|
+
const selectorResultRef = useRef3({
|
|
352
|
+
call: null,
|
|
353
|
+
selector: null
|
|
354
|
+
});
|
|
355
|
+
const selectorProxy = createSelectorProxy3((result2) => {
|
|
356
|
+
selectorResultRef.current = result2;
|
|
357
|
+
});
|
|
358
|
+
readFn(selectorProxy);
|
|
359
|
+
const capturedCall = selectorResultRef.current.call;
|
|
360
|
+
if (!capturedCall) {
|
|
361
|
+
throw new Error(
|
|
362
|
+
"useInfiniteRead requires calling an HTTP method ($get). Example: useInfiniteRead((api) => api.posts.$get())"
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const requestOptions = capturedCall.options;
|
|
366
|
+
const initialRequest = {
|
|
367
|
+
query: requestOptions?.query,
|
|
368
|
+
params: requestOptions?.params,
|
|
369
|
+
body: requestOptions?.body
|
|
370
|
+
};
|
|
371
|
+
const baseOptionsForKey = {
|
|
372
|
+
...capturedCall.options,
|
|
373
|
+
query: void 0,
|
|
374
|
+
params: void 0,
|
|
375
|
+
body: void 0
|
|
376
|
+
};
|
|
377
|
+
const resolvedPath = resolvePath3(capturedCall.path, requestOptions?.params);
|
|
378
|
+
const resolvedTags = resolveTags3({ tags, additionalTags }, resolvedPath);
|
|
379
|
+
const canFetchNextRef = useRef3(canFetchNext);
|
|
380
|
+
const canFetchPrevRef = useRef3(canFetchPrev);
|
|
381
|
+
const nextPageRequestRef = useRef3(nextPageRequest);
|
|
382
|
+
const prevPageRequestRef = useRef3(prevPageRequest);
|
|
383
|
+
const mergerRef = useRef3(merger);
|
|
384
|
+
canFetchNextRef.current = canFetchNext;
|
|
385
|
+
canFetchPrevRef.current = canFetchPrev;
|
|
386
|
+
nextPageRequestRef.current = nextPageRequest;
|
|
387
|
+
prevPageRequestRef.current = prevPageRequest;
|
|
388
|
+
mergerRef.current = merger;
|
|
389
|
+
const queryKey = stateManager.createQueryKey({
|
|
390
|
+
path: capturedCall.path,
|
|
391
|
+
method: capturedCall.method,
|
|
392
|
+
options: baseOptionsForKey
|
|
393
|
+
});
|
|
394
|
+
const controllerRef = useRef3(null);
|
|
395
|
+
if (!controllerRef.current || controllerRef.current.queryKey !== queryKey) {
|
|
396
|
+
controllerRef.current = {
|
|
397
|
+
controller: createInfiniteReadController({
|
|
398
|
+
path: capturedCall.path,
|
|
399
|
+
method: capturedCall.method,
|
|
400
|
+
tags: resolvedTags,
|
|
401
|
+
initialRequest,
|
|
402
|
+
baseOptionsForKey,
|
|
403
|
+
canFetchNext: (ctx) => canFetchNextRef.current(ctx),
|
|
404
|
+
canFetchPrev: canFetchPrev ? (ctx) => canFetchPrevRef.current?.(ctx) ?? false : void 0,
|
|
405
|
+
nextPageRequest: (ctx) => nextPageRequestRef.current(ctx),
|
|
406
|
+
prevPageRequest: prevPageRequest ? (ctx) => prevPageRequestRef.current?.(ctx) ?? {} : void 0,
|
|
407
|
+
merger: (responses) => mergerRef.current(responses),
|
|
408
|
+
stateManager,
|
|
409
|
+
eventEmitter,
|
|
410
|
+
pluginExecutor,
|
|
411
|
+
hookId,
|
|
412
|
+
fetchFn: async (opts, signal) => {
|
|
413
|
+
const fetchPath = resolvePath3(capturedCall.path, opts.params);
|
|
414
|
+
let current = api;
|
|
415
|
+
for (const segment of fetchPath) {
|
|
416
|
+
current = current[segment];
|
|
417
|
+
}
|
|
418
|
+
const method = current[capturedCall.method];
|
|
419
|
+
const fetchOptions = {
|
|
420
|
+
...capturedCall.options,
|
|
421
|
+
query: opts.query,
|
|
422
|
+
params: opts.params,
|
|
423
|
+
body: opts.body,
|
|
424
|
+
signal
|
|
425
|
+
};
|
|
426
|
+
return method(fetchOptions);
|
|
427
|
+
}
|
|
428
|
+
}),
|
|
429
|
+
queryKey
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const controller = controllerRef.current.controller;
|
|
433
|
+
controller.setPluginOptions(pluginOpts);
|
|
434
|
+
const state = useSyncExternalStore3(
|
|
435
|
+
controller.subscribe,
|
|
436
|
+
controller.getState,
|
|
437
|
+
controller.getState
|
|
438
|
+
);
|
|
439
|
+
const fetchingDirection = controller.getFetchingDirection();
|
|
440
|
+
const fetching = fetchingDirection !== null;
|
|
441
|
+
const fetchingNext = fetchingDirection === "next";
|
|
442
|
+
const fetchingPrev = fetchingDirection === "prev";
|
|
443
|
+
const loading = fetching && state.data === void 0;
|
|
444
|
+
const lifecycleRef = useRef3({
|
|
445
|
+
initialized: false,
|
|
446
|
+
prevContext: null
|
|
447
|
+
});
|
|
448
|
+
useEffect2(() => {
|
|
449
|
+
return () => {
|
|
450
|
+
controllerRef.current?.controller.unmount();
|
|
451
|
+
lifecycleRef.current.initialized = false;
|
|
452
|
+
};
|
|
453
|
+
}, []);
|
|
454
|
+
useEffect2(() => {
|
|
455
|
+
controller.mount();
|
|
456
|
+
lifecycleRef.current.initialized = true;
|
|
457
|
+
const unsubInvalidate = eventEmitter.on(
|
|
458
|
+
"invalidate",
|
|
459
|
+
(invalidatedTags) => {
|
|
460
|
+
const hasMatch = invalidatedTags.some(
|
|
461
|
+
(tag) => resolvedTags.includes(tag)
|
|
462
|
+
);
|
|
463
|
+
if (hasMatch) {
|
|
464
|
+
controller.refetch();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
);
|
|
468
|
+
return () => {
|
|
469
|
+
unsubInvalidate();
|
|
470
|
+
};
|
|
471
|
+
}, []);
|
|
472
|
+
useEffect2(() => {
|
|
473
|
+
if (!lifecycleRef.current.initialized) return;
|
|
474
|
+
if (enabled) {
|
|
475
|
+
const currentState = controller.getState();
|
|
476
|
+
const isFetching = controller.getFetchingDirection() !== null;
|
|
477
|
+
if (currentState.data === void 0 && !isFetching) {
|
|
478
|
+
controller.fetchNext();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}, [enabled]);
|
|
482
|
+
useEffect2(() => {
|
|
483
|
+
if (!enabled || !lifecycleRef.current.initialized) return;
|
|
484
|
+
const prevContext = controller.getContext();
|
|
485
|
+
controller.update(prevContext);
|
|
486
|
+
}, [JSON.stringify(pluginOpts)]);
|
|
487
|
+
const result = {
|
|
488
|
+
data: state.data,
|
|
489
|
+
allResponses: state.allResponses,
|
|
490
|
+
loading,
|
|
491
|
+
fetching,
|
|
492
|
+
fetchingNext,
|
|
493
|
+
fetchingPrev,
|
|
494
|
+
canFetchNext: state.canFetchNext,
|
|
495
|
+
canFetchPrev: state.canFetchPrev,
|
|
496
|
+
fetchNext: controller.fetchNext,
|
|
497
|
+
fetchPrev: controller.fetchPrev,
|
|
498
|
+
refetch: controller.refetch,
|
|
499
|
+
abort: controller.abort,
|
|
500
|
+
error: state.error
|
|
501
|
+
};
|
|
502
|
+
return result;
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/createReactSpoosh/index.ts
|
|
507
|
+
function createReactSpoosh(instance) {
|
|
508
|
+
const { api, stateManager, eventEmitter, pluginExecutor } = instance;
|
|
509
|
+
const useRead = createUseRead({
|
|
510
|
+
api,
|
|
511
|
+
stateManager,
|
|
512
|
+
eventEmitter,
|
|
513
|
+
pluginExecutor
|
|
514
|
+
});
|
|
515
|
+
const useWrite = createUseWrite({
|
|
516
|
+
api,
|
|
517
|
+
stateManager,
|
|
518
|
+
eventEmitter,
|
|
519
|
+
pluginExecutor
|
|
520
|
+
});
|
|
521
|
+
const useInfiniteRead = createUseInfiniteRead({
|
|
522
|
+
api,
|
|
523
|
+
stateManager,
|
|
524
|
+
eventEmitter,
|
|
525
|
+
pluginExecutor
|
|
526
|
+
});
|
|
527
|
+
const instanceApiContext = {
|
|
528
|
+
api,
|
|
529
|
+
stateManager,
|
|
530
|
+
eventEmitter,
|
|
531
|
+
pluginExecutor
|
|
532
|
+
};
|
|
533
|
+
const plugins = pluginExecutor.getPlugins();
|
|
534
|
+
const instanceApis = plugins.reduce(
|
|
535
|
+
(acc, plugin) => {
|
|
536
|
+
if (plugin.instanceApi) {
|
|
537
|
+
return { ...acc, ...plugin.instanceApi(instanceApiContext) };
|
|
538
|
+
}
|
|
539
|
+
return acc;
|
|
540
|
+
},
|
|
541
|
+
{}
|
|
542
|
+
);
|
|
543
|
+
return {
|
|
544
|
+
useRead,
|
|
545
|
+
useWrite,
|
|
546
|
+
useInfiniteRead,
|
|
547
|
+
...instanceApis
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
export {
|
|
551
|
+
createReactSpoosh,
|
|
552
|
+
createUseInfiniteRead,
|
|
553
|
+
createUseRead,
|
|
554
|
+
createUseWrite
|
|
555
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@spoosh/react",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "React hooks for Spoosh API client",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"spoosh",
|
|
8
|
+
"react",
|
|
9
|
+
"hooks",
|
|
10
|
+
"api-client",
|
|
11
|
+
"useQuery",
|
|
12
|
+
"useMutation"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/nxnom/spoosh.git",
|
|
17
|
+
"directory": "packages/react"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/nxnom/spoosh/issues"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://spoosh.dev/docs/integrations/react",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.mjs",
|
|
33
|
+
"require": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@spoosh/core": "0.1.0-beta.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": "^18 || ^19"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"lint": "eslint src --max-warnings 0",
|
|
47
|
+
"format": "prettier --write 'src/**/*.ts'"
|
|
48
|
+
}
|
|
49
|
+
}
|