@intrig/plugin-react 0.0.1 → 0.0.2-2

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.
Files changed (58) hide show
  1. package/dist/index.cjs +4260 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +4235 -0
  4. package/package.json +6 -3
  5. package/.swcrc +0 -29
  6. package/README.md +0 -7
  7. package/eslint.config.mjs +0 -19
  8. package/project.json +0 -29
  9. package/rollup.config.cjs +0 -54
  10. package/rollup.config.mjs +0 -33
  11. package/src/index.ts +0 -2
  12. package/src/lib/code-generator.ts +0 -79
  13. package/src/lib/get-endpoint-documentation.ts +0 -35
  14. package/src/lib/get-schema-documentation.ts +0 -11
  15. package/src/lib/internal-types.ts +0 -15
  16. package/src/lib/plugin-react.ts +0 -22
  17. package/src/lib/templates/context.template.ts +0 -74
  18. package/src/lib/templates/docs/__snapshots__/async-hook.spec.ts.snap +0 -889
  19. package/src/lib/templates/docs/__snapshots__/download-hook.spec.ts.snap +0 -1445
  20. package/src/lib/templates/docs/__snapshots__/react-hook.spec.ts.snap +0 -1371
  21. package/src/lib/templates/docs/__snapshots__/sse-hook.spec.ts.snap +0 -2008
  22. package/src/lib/templates/docs/async-hook.spec.ts +0 -92
  23. package/src/lib/templates/docs/async-hook.ts +0 -226
  24. package/src/lib/templates/docs/download-hook.spec.ts +0 -182
  25. package/src/lib/templates/docs/download-hook.ts +0 -170
  26. package/src/lib/templates/docs/react-hook.spec.ts +0 -97
  27. package/src/lib/templates/docs/react-hook.ts +0 -323
  28. package/src/lib/templates/docs/schema.ts +0 -105
  29. package/src/lib/templates/docs/sse-hook.spec.ts +0 -207
  30. package/src/lib/templates/docs/sse-hook.ts +0 -221
  31. package/src/lib/templates/extra.template.ts +0 -198
  32. package/src/lib/templates/index.template.ts +0 -14
  33. package/src/lib/templates/intrigMiddleware.template.ts +0 -21
  34. package/src/lib/templates/logger.template.ts +0 -67
  35. package/src/lib/templates/media-type-utils.template.ts +0 -191
  36. package/src/lib/templates/network-state.template.ts +0 -702
  37. package/src/lib/templates/packageJson.template.ts +0 -63
  38. package/src/lib/templates/provider/__tests__/provider-templates.spec.ts +0 -209
  39. package/src/lib/templates/provider/axios-config.template.ts +0 -49
  40. package/src/lib/templates/provider/hooks.template.ts +0 -240
  41. package/src/lib/templates/provider/interfaces.template.ts +0 -72
  42. package/src/lib/templates/provider/intrig-provider-stub.template.ts +0 -73
  43. package/src/lib/templates/provider/intrig-provider.template.ts +0 -185
  44. package/src/lib/templates/provider/main.template.ts +0 -48
  45. package/src/lib/templates/provider/reducer.template.ts +0 -50
  46. package/src/lib/templates/provider/status-trap.template.ts +0 -80
  47. package/src/lib/templates/provider.template.ts +0 -698
  48. package/src/lib/templates/source/controller/method/asyncFunctionHook.template.ts +0 -196
  49. package/src/lib/templates/source/controller/method/clientIndex.template.ts +0 -38
  50. package/src/lib/templates/source/controller/method/download.template.ts +0 -256
  51. package/src/lib/templates/source/controller/method/params.template.ts +0 -31
  52. package/src/lib/templates/source/controller/method/requestHook.template.ts +0 -220
  53. package/src/lib/templates/source/type/typeTemplate.ts +0 -257
  54. package/src/lib/templates/swcrc.template.ts +0 -25
  55. package/src/lib/templates/tsconfig.template.ts +0 -37
  56. package/src/lib/templates/type-utils.template.ts +0 -28
  57. package/tsconfig.json +0 -13
  58. package/tsconfig.lib.json +0 -20
@@ -1,698 +0,0 @@
1
- import {IntrigSourceConfig, typescript} from "@intrig/plugin-sdk";
2
- import * as path from 'path'
3
-
4
- export function reactProviderTemplate(apisToSync: IntrigSourceConfig[]) {
5
-
6
- const axiosConfigs = apisToSync.map(a => `
7
- ${a.id}: createAxiosInstance(configs.defaults, configs['${a.id}']),
8
- `).join("\n");
9
-
10
- const configType = `{
11
- defaults?: DefaultConfigs,
12
- ${apisToSync.map(a => `${a.id}?: DefaultConfigs`).join(",\n ")}
13
- }`
14
-
15
- const ts = typescript(path.resolve("src", "intrig-provider.tsx"))
16
- return ts`import React, {
17
- PropsWithChildren,
18
- useCallback,
19
- useContext,
20
- useMemo,
21
- useReducer,
22
- useState,
23
- useRef,
24
- } from 'react';
25
- import {
26
- error,
27
- ErrorState,
28
- ErrorWithContext,
29
- init,
30
- IntrigHook,
31
- isSuccess,
32
- isError,
33
- isPending,
34
- NetworkAction,
35
- NetworkState,
36
- pending,
37
- Progress,
38
- success, responseValidationError, configError, httpError, networkError,
39
- } from './network-state';
40
- import axios, {
41
- Axios,
42
- AxiosProgressEvent,
43
- AxiosResponse,
44
- CreateAxiosDefaults,
45
- InternalAxiosRequestConfig,
46
- isAxiosError,
47
- } from 'axios';
48
- import {ZodError, ZodSchema} from 'zod';
49
- import logger from './logger';
50
- import { flushSync } from 'react-dom';
51
- import { createParser } from 'eventsource-parser';
52
-
53
- import { Context, RequestType, GlobalState, SchemaOf } from './intrig-context';
54
-
55
- /**
56
- * Handles state updates for network requests based on the provided action.
57
- *
58
- * @param {GlobalState} state - The current state of the application.
59
- * @param {NetworkAction<unknown>} action - The action containing source, operation, key, and state.
60
- * @return {GlobalState} - The updated state after applying the action.
61
- */
62
- function requestReducer(
63
- state: GlobalState,
64
- action: NetworkAction<unknown>,
65
- ): GlobalState {
66
- return {
67
- ...state,
68
- [${"`${action.source}:${action.operation}:${action.key}`"}]: action.state,
69
- };
70
- }
71
-
72
- export interface DefaultConfigs extends CreateAxiosDefaults {
73
- debounceDelay?: number;
74
- requestInterceptor?: (
75
- config: InternalAxiosRequestConfig,
76
- ) => Promise<InternalAxiosRequestConfig>;
77
- responseInterceptor?: (
78
- config: AxiosResponse<any>,
79
- ) => Promise<AxiosResponse<any>>;
80
- }
81
-
82
- export interface IntrigProviderProps {
83
- configs?: ${configType};
84
- children: React.ReactNode;
85
- }
86
-
87
- function createAxiosInstance(
88
- defaultConfig?: DefaultConfigs,
89
- config?: DefaultConfigs,
90
- ) {
91
- const axiosInstance = axios.create({
92
- ...(defaultConfig ?? {}),
93
- ...(config ?? {}),
94
- });
95
- async function requestInterceptor(cfg: InternalAxiosRequestConfig) {
96
- let intermediate = (await defaultConfig?.requestInterceptor?.(cfg)) ?? cfg;
97
- return config?.requestInterceptor?.(intermediate) ?? intermediate;
98
- }
99
-
100
- async function responseInterceptor(cfg: AxiosResponse<any>) {
101
- let intermediate = (await defaultConfig?.responseInterceptor?.(cfg)) ?? cfg;
102
- return config?.responseInterceptor?.(intermediate) ?? intermediate;
103
- }
104
-
105
- axiosInstance.interceptors.request.use(requestInterceptor);
106
- axiosInstance.interceptors.response.use(responseInterceptor, (error) => {
107
- return Promise.reject(error);
108
- });
109
- return axiosInstance;
110
- }
111
-
112
- function inferNetworkReason(e: any): 'timeout' | 'dns' | 'offline' | 'aborted' | 'unknown' {
113
- if (e?.code === 'ECONNABORTED') return 'timeout';
114
- if (typeof navigator !== 'undefined' && navigator.onLine === false) return 'offline';
115
- if (e?.name === 'AbortError') return 'aborted';
116
- return 'unknown';
117
- }
118
-
119
- /**
120
- * IntrigProvider is a context provider component that sets up global state management
121
- * and provides Axios instances for API requests.
122
- *
123
- * @param {Object} props - The properties object.
124
- * @param {React.ReactNode} props.children - The child components to be wrapped by the provider.
125
- * @param {Object} [props.configs={}] - Configuration object for Axios instances.
126
- * @param {Object} [props.configs.defaults={}] - Default configuration for Axios.
127
- * @param {Object} [props.configs.petstore={}] - Configuration specific to the petstore API.
128
- * @return {JSX.Element} A context provider component that wraps the provided children.
129
- */
130
- export function IntrigProvider({
131
- children,
132
- configs = {},
133
- }: IntrigProviderProps) {
134
- const [state, dispatch] = useReducer(requestReducer, {} as GlobalState);
135
-
136
- const axiosInstances: Record<string, Axios> = useMemo(() => {
137
- return {
138
- ${axiosConfigs}
139
- }
140
- }, [configs]);
141
-
142
- const contextValue = useMemo(() => {
143
- async function execute<T>(
144
- request: RequestType,
145
- setState: (s: NetworkState<T>) => void,
146
- schema?: ZodSchema<T>, // success payload schema (optional)
147
- errorSchema?: ZodSchema<any>, // error-body schema for non-2xx (optional)
148
- ) {
149
- try {
150
- setState(pending());
151
-
152
- const axios = axiosInstances[request.source];
153
- if (!axios) {
154
- setState(error(configError(${"`Unknown axios source '${request.source}'`"})));
155
- return;
156
- }
157
-
158
- const response = await axios.request(request);
159
- const status = response.status;
160
- const method = (response.config?.method || 'GET').toUpperCase();
161
- const url = response.config?.url || '';
162
- const ctype = String(response.headers?.['content-type'] || '');
163
-
164
- // -------------------- 2xx branch --------------------
165
- if (status >= 200 && status < 300) {
166
- // SSE stream
167
- if (ctype.includes('text/event-stream')) {
168
- const reader = response.data.getReader();
169
- const decoder = new TextDecoder();
170
-
171
- let lastMessage: any;
172
-
173
- const parser = createParser({
174
- onEvent(evt) {
175
- let decoded: unknown = evt.data;
176
-
177
- // Try JSON parse; if schema is defined, we require valid JSON for validation
178
- try {
179
- let parsed: unknown = JSON.parse(String(decoded));
180
- if (schema) {
181
- const vr = schema.safeParse(parsed);
182
- if (!vr.success) {
183
- setState(error(responseValidationError(vr.error, parsed)));
184
- return;
185
- }
186
- parsed = vr.data;
187
- }
188
- decoded = parsed;
189
- } catch (ignore) {
190
- if (schema) {
191
- // schema expects structured data but chunk wasn’t JSON
192
- setState(error(responseValidationError(new ZodError([]), decoded)));
193
- return;
194
- }
195
- // if no schema, pass raw text
196
- }
197
-
198
- lastMessage = decoded;
199
- flushSync(() => setState(pending(undefined, decoded as T)));
200
- },
201
- });
202
-
203
- while (true) {
204
- const { done, value } = await reader.read();
205
- if (done) {
206
- flushSync(() => setState(success(lastMessage as T, response.headers)));
207
- break;
208
- }
209
- parser.feed(decoder.decode(value, { stream: true }));
210
- }
211
- return;
212
- }
213
-
214
- // Non-SSE: validate body if a schema is provided
215
- if (schema) {
216
- const parsed = schema.safeParse(response.data);
217
- if (!parsed.success) {
218
- setState(error(responseValidationError(parsed.error, response.data)));
219
- return;
220
- }
221
- setState(success(parsed.data, response.headers));
222
- return;
223
- }
224
-
225
- // No schema → pass through
226
- setState(success(response.data as T, response.headers));
227
- return;
228
- }
229
-
230
- // -------------------- non-2xx (HTTP error) --------------------
231
- let errorBody: unknown = response.data;
232
-
233
- if (errorSchema) {
234
- const ev = errorSchema.safeParse(errorBody ?? {});
235
- if (!ev.success) {
236
- setState(error(responseValidationError(ev.error, errorBody)));
237
- return;
238
- }
239
- errorBody = ev.data;
240
- }
241
-
242
- // NOTE: your httpError signature is (status, url, method, headers?, body?)
243
- setState(error(httpError(status, url, method, response.headers, errorBody)));
244
-
245
- } catch (e: any) {
246
- // -------------------- thrown / transport --------------------
247
- if (isAxiosError(e)) {
248
- const status = e.response?.status;
249
- const method = (e.config?.method || 'GET').toUpperCase();
250
- const url = e.config?.url || '';
251
-
252
- if (status != null) {
253
- // HTTP error with response
254
- let errorBody: unknown = e.response?.data;
255
-
256
- if (errorSchema) {
257
- const ev = errorSchema.safeParse(errorBody ?? {});
258
- if (!ev.success) {
259
- setState(error(responseValidationError(ev.error, errorBody)));
260
- return;
261
- }
262
- errorBody = ev.data;
263
- }
264
-
265
- setState(error(httpError(status, url, method, e.response?.headers, errorBody)));
266
- return;
267
- }
268
-
269
- // No response → network layer
270
- setState(error(networkError(inferNetworkReason(e), e.request)));
271
- return;
272
- }
273
-
274
- // Non-Axios exception → treat as unknown network-ish failure
275
- setState(error(networkError('unknown')));
276
- }
277
- }
278
- return {
279
- state,
280
- dispatch,
281
- filteredState: state,
282
- configs,
283
- execute,
284
- };
285
- }, [state, axiosInstances]);
286
-
287
- return <Context.Provider value={contextValue}>{children}</Context.Provider>;
288
- }
289
-
290
- export interface StubType {
291
- <P, B, T>(
292
- hook: IntrigHook<P, B, T>,
293
- fn: (
294
- params: P,
295
- body: B,
296
- dispatch: (state: NetworkState<T>) => void,
297
- ) => Promise<void>,
298
- ): void;
299
- }
300
-
301
- export type WithStubSupport<T> = T & {
302
- stubs?: (stub: StubType) => void;
303
- };
304
-
305
- export interface IntrigProviderStubProps {
306
- configs?: {
307
- defaults?: DefaultConfigs;
308
- employee_api?: DefaultConfigs;
309
- };
310
- stubs?: (stub: StubType) => void;
311
- children: React.ReactNode;
312
- }
313
-
314
- export function IntrigProviderStub({
315
- children,
316
- configs = {},
317
- stubs = () => {},
318
- }: IntrigProviderStubProps) {
319
- const [state, dispatch] = useReducer(requestReducer, {} as GlobalState);
320
-
321
- const collectedStubs = useMemo(() => {
322
- let fns: Record<
323
- string,
324
- (
325
- params: any,
326
- body: any,
327
- dispatch: (state: NetworkState<any>) => void,
328
- ) => Promise<void>
329
- > = {};
330
- function stub<P, B, T>(
331
- hook: IntrigHook<P, B, T>,
332
- fn: (
333
- params: P,
334
- body: B,
335
- dispatch: (state: NetworkState<T>) => void,
336
- ) => Promise<void>,
337
- ) {
338
- fns[hook.key] = fn;
339
- }
340
- stubs(stub);
341
- return fns;
342
- }, [stubs]);
343
-
344
- const contextValue = useMemo(() => {
345
- async function execute<T>(
346
- request: RequestType,
347
- dispatch: (state: NetworkState<T>) => void,
348
- schema: ZodSchema<T> | undefined,
349
- ) {
350
- let stub = collectedStubs[request.key];
351
-
352
- if (!!stub) {
353
- try {
354
- await stub(request.params, request.data, dispatch);
355
- } catch (e: any) {
356
- dispatch(error(configError(e?.message ?? '')));
357
- }
358
- } else {
359
- dispatch(init());
360
- }
361
- }
362
-
363
- return {
364
- state,
365
- dispatch,
366
- filteredState: state,
367
- configs,
368
- execute,
369
- };
370
- }, [state, dispatch, configs, collectedStubs]);
371
-
372
- return <Context.Provider value={contextValue}>{children}</Context.Provider>;
373
- }
374
-
375
- export interface StatusTrapProps {
376
- type: 'pending' | 'error' | 'pending + error';
377
- propagate?: boolean;
378
- }
379
-
380
- /**
381
- * StatusTrap component is used to track and manage network request states.
382
- *
383
- * @param {Object} props - The properties object.
384
- * @param {React.ReactNode} props.children - The child elements to be rendered.
385
- * @param {string} props.type - The type of network state to handle ("error", "pending", "pending + error").
386
- * @param {boolean} [props.propagate=true] - Whether to propagate the event to the parent context.
387
- * @return {React.ReactElement} The context provider component with filtered state and custom dispatch.
388
- */
389
- export function StatusTrap({
390
- children,
391
- type,
392
- propagate = true,
393
- }: PropsWithChildren<StatusTrapProps>) {
394
- const ctx = useContext(Context);
395
-
396
- const [requests, setRequests] = useState<string[]>([]);
397
-
398
- const shouldHandleEvent = useCallback(
399
- (state: NetworkState) => {
400
- switch (type) {
401
- case 'error':
402
- return isError(state);
403
- case 'pending':
404
- return isPending(state);
405
- case 'pending + error':
406
- return isPending(state) || isError(state);
407
- default:
408
- return false;
409
- }
410
- },
411
- [type],
412
- );
413
-
414
- const dispatch = useCallback(
415
- (event: NetworkAction<any>) => {
416
- if (!event.handled) {
417
- if (shouldHandleEvent(event.state)) {
418
- setRequests((prev) => [...prev, event.key]);
419
- if (!propagate) {
420
- ctx.dispatch({
421
- ...event,
422
- handled: true,
423
- });
424
- return;
425
- }
426
- } else {
427
- setRequests((prev) => prev.filter((k) => k !== event.key));
428
- }
429
- }
430
- ctx.dispatch(event);
431
- },
432
- [ctx, propagate, shouldHandleEvent],
433
- );
434
-
435
- const filteredState = useMemo(() => {
436
- return Object.fromEntries(
437
- Object.entries(ctx.state).filter(([key]) => requests.includes(key)),
438
- );
439
- }, [ctx.state, requests]);
440
-
441
- return (
442
- <Context.Provider
443
- value={{
444
- ...ctx,
445
- dispatch,
446
- filteredState,
447
- }}
448
- >
449
- {children}
450
- </Context.Provider>
451
- );
452
- }
453
-
454
- export interface NetworkStateProps<T> {
455
- key: string;
456
- operation: string;
457
- source: string;
458
- schema?: SchemaOf<T>;
459
- errorSchema?: SchemaOf<any>;
460
- debounceDelay?: number;
461
- }
462
-
463
- /**
464
- * useNetworkState is a custom hook that manages the network state within the specified context.
465
- * It handles making network requests, dispatching appropriate states based on the request lifecycle,
466
- * and allows aborting ongoing requests.
467
- *
468
- * @param {Object} params - The parameters required to configure and use the network state.
469
- * @param {string} params.key - A unique identifier for the network request.
470
- * @param {string} params.operation - The operation type related to the request.
471
- * @param {string} params.source - The source or endpoint for the network request.
472
- * @param {Object} params.schema - The schema used for validating the response data.
473
- * @param {number} [params.debounceDelay] - The debounce delay for executing the network request.
474
- *
475
- * @return {[NetworkState<T>, (request: AxiosRequestConfig) => void, () => void]}
476
- * Returns a state object representing the current network state,
477
- * a function to execute the network request, and a function to clear the request.
478
- */
479
- export function useNetworkState<T>({
480
- key,
481
- operation,
482
- source,
483
- schema,
484
- errorSchema,
485
- debounceDelay: requestDebounceDelay,
486
- }: NetworkStateProps<T>): [
487
- NetworkState<T>,
488
- (request: RequestType) => void,
489
- () => void,
490
- (state: NetworkState<T>) => void,
491
- ] {
492
- const context = useContext(Context);
493
-
494
- const [abortController, setAbortController] = useState<AbortController>();
495
-
496
- const networkState = useMemo(() => {
497
- logger.info(${"`Updating status ${key} ${operation} ${source}`"});
498
- logger.debug("<=", context.state?.[${"`${source}:${operation}:${key}`"}])
499
- return (
500
- (context.state?.[${"`${source}:${operation}:${key}`"}] as NetworkState<T>) ??
501
- init()
502
- );
503
- }, [JSON.stringify(context.state?.[${"`${source}:${operation}:${key}`"}])]);
504
-
505
- const dispatch = useCallback(
506
- (state: NetworkState<T>) => {
507
- context.dispatch({ key, operation, source, state });
508
- },
509
- [key, operation, source, context.dispatch],
510
- );
511
-
512
- const debounceDelay = useMemo(() => {
513
- return (
514
- requestDebounceDelay ?? context.configs?.[source]?.debounceDelay ?? 0
515
- );
516
- }, [context.configs, requestDebounceDelay, source]);
517
-
518
- const execute = useCallback(
519
- async (request: RequestType) => {
520
- logger.info(${"`Executing request ${key} ${operation} ${source}`"});
521
- logger.debug("=>", request)
522
-
523
- let abortController = new AbortController();
524
- setAbortController(abortController);
525
-
526
- let requestConfig: RequestType = {
527
- ...request,
528
- onUploadProgress(event: AxiosProgressEvent) {
529
- dispatch(
530
- pending({
531
- type: 'upload',
532
- loaded: event.loaded,
533
- total: event.total,
534
- }),
535
- );
536
- request.onUploadProgress?.(event);
537
- },
538
- onDownloadProgress(event: AxiosProgressEvent) {
539
- dispatch(
540
- pending({
541
- type: 'download',
542
- loaded: event.loaded,
543
- total: event.total,
544
- }),
545
- );
546
- request.onDownloadProgress?.(event);
547
- },
548
- signal: abortController.signal,
549
- };
550
-
551
- await context.execute(
552
- requestConfig,
553
- dispatch,
554
- schema,
555
- errorSchema as any,
556
- );
557
- },
558
- [networkState, context.dispatch, axios],
559
- );
560
-
561
- const deboundedExecute = useMemo(
562
- () => debounce(execute, debounceDelay ?? 0),
563
- [execute],
564
- );
565
-
566
- const clear = useCallback(() => {
567
- logger.info(${"`Clearing request ${key} ${operation} ${source}`"});
568
- dispatch(init());
569
- setAbortController((abortController) => {
570
- logger.info(${"`Aborting request ${key} ${operation} ${source}`"});
571
- abortController?.abort();
572
- return undefined;
573
- });
574
- }, [dispatch, abortController]);
575
-
576
- return [networkState, deboundedExecute, clear, dispatch];
577
- }
578
-
579
- /**
580
- * A hook for making transient calls that can be aborted and validated against schemas.
581
- *
582
- * @param {Object} options The options object.
583
- * @param {ZodSchema<T>} [options.schema] Optional schema to validate the response data.
584
- * @param {ZodSchema<T>} [options.errorSchema] Optional schema to validate the error response data.
585
- * @return {[function(RequestType): Promise<T>, function(): void]} Returns a tuple containing a function to execute the request and a function to abort the ongoing request.
586
- */
587
- export function useTransitionCall<T>({
588
- schema,
589
- errorSchema,
590
- }: {
591
- schema?: SchemaOf<T>;
592
- errorSchema?: SchemaOf<T>;
593
- }): [(request: RequestType) => Promise<T>, () => void] {
594
- const ctx = useContext(Context);
595
- const controller = useRef<AbortController | undefined>(undefined);
596
-
597
- const call = useCallback(
598
- async (request: RequestType) => {
599
- controller.current?.abort();
600
- const abort = new AbortController();
601
- controller.current = abort;
602
-
603
- return new Promise<T>((resolve, reject) => {
604
- ctx.execute(
605
- { ...request, signal: abort.signal },
606
- (state) => {
607
- if (isSuccess(state)) {
608
- resolve(state.data as T);
609
- } else if (isError(state)) {
610
- reject(state.error);
611
- }
612
- },
613
- schema,
614
- errorSchema,
615
- );
616
- });
617
- },
618
- [ctx, schema, errorSchema],
619
- );
620
-
621
- const abort = useCallback(() => {
622
- controller.current?.abort();
623
- }, []);
624
-
625
- return [call, abort];
626
- }
627
-
628
- function debounce<T extends (...args: any[]) => void>(func: T, delay: number) {
629
- let timeoutId: any;
630
-
631
- return (...args: Parameters<T>) => {
632
- if (timeoutId) {
633
- clearTimeout(timeoutId);
634
- }
635
- timeoutId = setTimeout(() => {
636
- func(...args);
637
- }, delay);
638
- };
639
- }
640
-
641
- /**
642
- * Handles central error extraction from the provided context.
643
- * It filters the state to retain error states and maps them to a structured error object with additional context information.
644
- * @return {Object[]} An array of objects representing the error states with context information such as source, operation, and key.
645
- */
646
- export function useCentralError() {
647
- const ctx = useContext(Context);
648
-
649
- return useMemo(() => {
650
- return Object.entries(ctx.filteredState as Record<string, NetworkState>)
651
- .filter(([, state]) => isError(state))
652
- .map(([k, state]) => {
653
- let [source, operation, key] = k.split(':');
654
- return {
655
- ...(state as ErrorState<unknown>),
656
- source,
657
- operation,
658
- key,
659
- } satisfies ErrorWithContext;
660
- });
661
- }, [ctx.filteredState]);
662
- }
663
-
664
- /**
665
- * Uses central pending state handling by aggregating pending states from context.
666
- * It calculates the overall progress of pending states if any, or returns an initial state otherwise.
667
- *
668
- * @return {NetworkState} The aggregated network state based on the pending states and their progress.
669
- */
670
- export function useCentralPendingState() {
671
- const ctx = useContext(Context);
672
-
673
- const result: NetworkState = useMemo(() => {
674
- let pendingStates = Object.values(
675
- ctx.filteredState as Record<string, NetworkState>,
676
- ).filter(isPending);
677
- if (!pendingStates.length) {
678
- return init();
679
- }
680
-
681
- let progress = pendingStates
682
- .filter((a) => a.progress)
683
- .reduce(
684
- (progress, current) => {
685
- return {
686
- total: progress.total + (current.progress?.total ?? 0),
687
- loaded: progress.loaded + (current.progress?.loaded ?? 0),
688
- };
689
- },
690
- { total: 0, loaded: 0 } satisfies Progress,
691
- );
692
- return pending(!!progress.total ? progress : undefined);
693
- }, [ctx.filteredState]);
694
-
695
- return result;
696
- }
697
- `
698
- }