@intrig/next 1.0.7 → 1.0.10

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/eslint.config.cjs +19 -0
  2. package/jest.config.ts +10 -0
  3. package/package.json +5 -7
  4. package/project.json +20 -0
  5. package/src/extra/{index.d.ts → index.ts} +2 -2
  6. package/src/extra/useAsNetworkState.ts +53 -0
  7. package/src/extra/{useAsPromise.d.ts → useAsPromise.ts} +58 -7
  8. package/src/extra/useLocalReducer.ts +61 -0
  9. package/src/extra/{useResolvedCachedValue.d.ts → useResolvedCachedValue.ts} +39 -7
  10. package/src/extra/{useResolvedValue.d.ts → useResolvedValue.ts} +39 -7
  11. package/src/extra.ts +190 -0
  12. package/src/{index.d.ts → index.ts} +1 -1
  13. package/src/intrig-context.ts +66 -0
  14. package/src/intrig-layout.tsx +18 -0
  15. package/src/intrig-middleware.ts +31 -0
  16. package/src/intrig-provider.tsx +454 -0
  17. package/src/logger.ts +13 -0
  18. package/src/media-type-utils.ts +184 -0
  19. package/src/{network-state.d.ts → network-state.tsx} +174 -92
  20. package/tsconfig.json +28 -0
  21. package/tsconfig.lib.json +10 -0
  22. package/tsconfig.spec.json +14 -0
  23. package/src/extra/index.js +0 -5
  24. package/src/extra/index.js.map +0 -1
  25. package/src/extra/useAsNetworkState.d.ts +0 -13
  26. package/src/extra/useAsNetworkState.js +0 -41
  27. package/src/extra/useAsNetworkState.js.map +0 -1
  28. package/src/extra/useAsPromise.js +0 -30
  29. package/src/extra/useAsPromise.js.map +0 -1
  30. package/src/extra/useLocalReducer.d.ts +0 -6
  31. package/src/extra/useLocalReducer.js +0 -50
  32. package/src/extra/useLocalReducer.js.map +0 -1
  33. package/src/extra/useResolvedCachedValue.js +0 -15
  34. package/src/extra/useResolvedCachedValue.js.map +0 -1
  35. package/src/extra/useResolvedValue.js +0 -17
  36. package/src/extra/useResolvedValue.js.map +0 -1
  37. package/src/extra.d.ts +0 -52
  38. package/src/extra.js +0 -92
  39. package/src/extra.js.map +0 -1
  40. package/src/index.js +0 -4
  41. package/src/index.js.map +0 -1
  42. package/src/intrig-context.d.ts +0 -42
  43. package/src/intrig-context.js +0 -21
  44. package/src/intrig-context.js.map +0 -1
  45. package/src/intrig-middleware.d.ts +0 -2
  46. package/src/intrig-middleware.js +0 -24
  47. package/src/intrig-middleware.js.map +0 -1
  48. package/src/intrig-provider.d.ts +0 -102
  49. package/src/intrig-provider.js +0 -296
  50. package/src/intrig-provider.js.map +0 -1
  51. package/src/logger.d.ts +0 -7
  52. package/src/logger.js +0 -11
  53. package/src/logger.js.map +0 -1
  54. package/src/media-type-utils.d.ts +0 -4
  55. package/src/media-type-utils.js +0 -121
  56. package/src/media-type-utils.js.map +0 -1
  57. package/src/network-state.js +0 -185
  58. package/src/network-state.js.map +0 -1
@@ -0,0 +1,454 @@
1
+ "use client"
2
+ import {
3
+ createContext,
4
+ PropsWithChildren,
5
+ useCallback,
6
+ useContext,
7
+ useMemo,
8
+ useReducer,
9
+ useState,
10
+ } from 'react';
11
+ import {
12
+ error,
13
+ ErrorState,
14
+ ErrorWithContext,
15
+ init, IntrigHook,
16
+ isError,
17
+ isPending,
18
+ NetworkAction,
19
+ NetworkState,
20
+ pending,
21
+ Progress,
22
+ success
23
+ } from './network-state';
24
+ import axios, {
25
+ Axios,
26
+ AxiosProgressEvent,
27
+ AxiosRequestConfig,
28
+ CreateAxiosDefaults,
29
+ isAxiosError,
30
+ } from 'axios';
31
+ import { ZodSchema } from 'zod';
32
+ import logger from './logger'
33
+
34
+ import {Context, RequestType, GlobalState} from './intrig-context';
35
+
36
+ /**
37
+ * Handles state updates for network requests based on the provided action.
38
+ *
39
+ * @param {GlobalState} state - The current state of the application.
40
+ * @param {NetworkAction<unknown>} action - The action containing source, operation, key, and state.
41
+ * @return {GlobalState} - The updated state after applying the action.
42
+ */
43
+ function requestReducer(
44
+ state: GlobalState,
45
+ action: NetworkAction<unknown, unknown>
46
+ ): GlobalState {
47
+ return {
48
+ ...state,
49
+ [`${action.source}:${action.operation}:${action.key}`]: action.state,
50
+ };
51
+ }
52
+
53
+ export interface DefaultConfigs extends CreateAxiosDefaults {
54
+ debounceDelay?: number;
55
+ }
56
+
57
+ export interface IntrigProviderProps {
58
+ configs?: DefaultConfigs;
59
+ children: React.ReactNode;
60
+ initState?: GlobalState;
61
+ }
62
+
63
+ /**
64
+ * IntrigProvider is a context provider component that sets up global state management
65
+ * and provides Axios instances for API requests.
66
+ *
67
+ * @param {Object} props - The properties object.
68
+ * @param {React.ReactNode} props.children - The child components to be wrapped by the provider.
69
+ * @param {Object} [props.configs={}] - Configuration object for Axios instances.
70
+ * @param {Object} [props.configs.defaults={}] - Default configuration for Axios.
71
+ * @param {Object} [props.configs.petstore={}] - Configuration specific to the petstore API.
72
+ * @return {JSX.Element} A context provider component that wraps the provided children.
73
+ */
74
+ export function IntrigProvider({
75
+ children,
76
+ configs = {},
77
+ initState = {},
78
+ }: IntrigProviderProps) {
79
+ const [state, dispatch] = useReducer(requestReducer, initState);
80
+
81
+ const axiosInstance: Axios = useMemo(() => {
82
+ return axios.create({
83
+ ...(configs ?? {}),
84
+ });
85
+ }, [configs]);
86
+
87
+ const contextValue = useMemo(() => {
88
+ async function execute<T, E = unknown>(request: RequestType, dispatch: (state: NetworkState<T, E>) => void, schema: ZodSchema<T> | undefined, errorSchema: ZodSchema<E> | undefined) {
89
+ try {
90
+ dispatch(pending());
91
+ const response = await axiosInstance.request(request);
92
+
93
+ if (response.status >= 200 && response.status < 300) {
94
+ if (schema) {
95
+ const data = schema.safeParse(response.data);
96
+ if (!data.success) {
97
+ dispatch(
98
+ error(data.error.issues, response.status, request)
99
+ );
100
+ return;
101
+ }
102
+ dispatch(success(data.data));
103
+ } else {
104
+ dispatch(success(response.data));
105
+ }
106
+ } else {
107
+ const { data } = errorSchema?.safeParse(response.data ?? {}) ?? {};
108
+ //todo: handle error validation error.
109
+ dispatch(
110
+ error(data ?? response.data ?? response.statusText, response.status)
111
+ );
112
+ }
113
+ } catch (e: any) {
114
+ if (isAxiosError(e)) {
115
+ const { data } = errorSchema?.safeParse(e.response?.data ?? {}) ?? {};
116
+ dispatch(error(data ?? e.response?.data, e.response?.status, request));
117
+ } else {
118
+ dispatch(error(e));
119
+ }
120
+ }
121
+ }
122
+
123
+ return {
124
+ state,
125
+ dispatch,
126
+ filteredState: state,
127
+ configs,
128
+ execute,
129
+ };
130
+ }, [state, axiosInstance]);
131
+
132
+ return <Context.Provider value={contextValue}>{children}</Context.Provider>;
133
+ }
134
+
135
+ export interface StubType {
136
+ <P, B, T>(hook: IntrigHook<P, B, T>, fn: (params: P, body: B, dispatch: (state: NetworkState<T>) => void) => Promise<void>): void
137
+ }
138
+
139
+ export type WithStubSupport<T> = T & {
140
+ stubs?: (stub: StubType) => void;
141
+ }
142
+
143
+ export interface IntrigProviderStubProps {
144
+ configs?: DefaultConfigs;
145
+ stubs?: (stub: StubType) => void;
146
+ children: React.ReactNode;
147
+ }
148
+
149
+ export function IntrigProviderStub({ children, configs = {}, stubs = () => {} }: IntrigProviderStubProps) {
150
+ const [state, dispatch] = useReducer(requestReducer, {} as GlobalState);
151
+
152
+ const collectedStubs = useMemo(() => {
153
+ const fns: Record<string, (params: any, body: any, dispatch: (state: NetworkState<any>) => void) => Promise<void>> = {};
154
+ function stub<P, B, T>(hook: IntrigHook<P, B, T>, fn: (params: P, body: B, dispatch: (state: NetworkState<T>) => void) => Promise<void>) {
155
+ fns[hook.key] = fn;
156
+ }
157
+ stubs(stub);
158
+ return fns
159
+ }, [stubs]);
160
+
161
+ const contextValue = useMemo(() => {
162
+
163
+ async function execute<T>(request: RequestType, dispatch: (state: NetworkState<T>) => void, schema: ZodSchema<T> | undefined) {
164
+ const stub = collectedStubs[request.key];
165
+
166
+ if (stub) {
167
+ try {
168
+ await stub(request.params, request.data, dispatch);
169
+ } catch (e) {
170
+ dispatch(error(e));
171
+ }
172
+ } else {
173
+ dispatch(init())
174
+ }
175
+ }
176
+
177
+ return {
178
+ state,
179
+ dispatch,
180
+ filteredState: state,
181
+ configs,
182
+ execute,
183
+ };
184
+ }, [state, dispatch, configs, collectedStubs]);
185
+
186
+ return <Context.Provider value={contextValue}>
187
+ {children}
188
+ </Context.Provider>
189
+ }
190
+
191
+ export interface StatusTrapProps {
192
+ type: 'pending' | 'error' | 'pending + error';
193
+ propagate?: boolean;
194
+ }
195
+
196
+ /**
197
+ * StatusTrap component is used to track and manage network request states.
198
+ *
199
+ * @param {Object} props - The properties object.
200
+ * @param {React.ReactNode} props.children - The child elements to be rendered.
201
+ * @param {string} props.type - The type of network state to handle ("error", "pending", "pending + error").
202
+ * @param {boolean} [props.propagate=true] - Whether to propagate the event to the parent context.
203
+ * @return {React.ReactElement} The context provider component with filtered state and custom dispatch.
204
+ */
205
+ export function StatusTrap({
206
+ children,
207
+ type,
208
+ propagate = true,
209
+ }: PropsWithChildren<StatusTrapProps>) {
210
+ const ctx = useContext(Context);
211
+
212
+ const [requests, setRequests] = useState<string[]>([]);
213
+
214
+ const shouldHandleEvent = useCallback(
215
+ (state: NetworkState) => {
216
+ switch (type) {
217
+ case 'error':
218
+ return isError(state);
219
+ case 'pending':
220
+ return isPending(state);
221
+ case 'pending + error':
222
+ return isPending(state) || isError(state);
223
+ default:
224
+ return false;
225
+ }
226
+ },
227
+ [type]
228
+ );
229
+
230
+ const dispatch = useCallback(
231
+ (event: NetworkAction<any, any>) => {
232
+ if (!event.handled) {
233
+ if (shouldHandleEvent(event.state)) {
234
+ setRequests((prev) => [...prev, event.key]);
235
+ if (!propagate) {
236
+ ctx.dispatch({
237
+ ...event,
238
+ handled: true,
239
+ });
240
+ return;
241
+ }
242
+ } else {
243
+ setRequests((prev) => prev.filter((k) => k !== event.key));
244
+ }
245
+ }
246
+ ctx.dispatch(event);
247
+ },
248
+ [ctx, propagate, shouldHandleEvent]
249
+ );
250
+
251
+ const filteredState = useMemo(() => {
252
+ return Object.fromEntries(
253
+ Object.entries(ctx.state).filter(([key]) => requests.includes(key))
254
+ );
255
+ }, [ctx.state, requests]);
256
+
257
+ return (
258
+ <Context.Provider
259
+ value={{
260
+ ...ctx,
261
+ dispatch,
262
+ filteredState,
263
+ }}
264
+ >
265
+ {children}
266
+ </Context.Provider>
267
+ );
268
+ }
269
+
270
+ export interface NetworkStateProps<T, E = unknown> {
271
+ key: string;
272
+ operation: string;
273
+ source: string;
274
+ schema?: ZodSchema<T>;
275
+ errorSchema?: ZodSchema<E>;
276
+ debounceDelay?: number;
277
+ }
278
+
279
+ /**
280
+ * useNetworkState is a custom hook that manages the network state within the specified context.
281
+ * It handles making network requests, dispatching appropriate states based on the request lifecycle,
282
+ * and allows aborting ongoing requests.
283
+ *
284
+ * @param {Object} params - The parameters required to configure and use the network state.
285
+ * @param {string} params.key - A unique identifier for the network request.
286
+ * @param {string} params.operation - The operation type related to the request.
287
+ * @param {string} params.source - The source or endpoint for the network request.
288
+ * @param {Object} params.schema - The schema used for validating the response data.
289
+ * @param {number} [params.debounceDelay] - The debounce delay for executing the network request.
290
+ *
291
+ * @return {[NetworkState<T>, (request: AxiosRequestConfig) => void, () => void]}
292
+ * Returns a state object representing the current network state,
293
+ * a function to execute the network request, and a function to clear the request.
294
+ */
295
+ export function useNetworkState<T, E = unknown>({
296
+ key,
297
+ operation,
298
+ source,
299
+ schema,
300
+ errorSchema,
301
+ debounceDelay: requestDebounceDelay,
302
+ }: NetworkStateProps<T>): [
303
+ NetworkState<T, E>,
304
+ (request: RequestType) => void,
305
+ clear: () => void,
306
+ (state: NetworkState<T, E>) => void
307
+ ] {
308
+ const context = useContext(Context);
309
+
310
+ const [abortController, setAbortController] = useState<AbortController>();
311
+
312
+ const networkState = useMemo(() => {
313
+ logger.info(`Updating status ${key} ${operation} ${source}`);
314
+ logger.debug(`⇦`, context.state?.[`${source}:${operation}:${key}`])
315
+ return (
316
+ (context.state?.[`${source}:${operation}:${key}`] as NetworkState<T>) ??
317
+ init()
318
+ );
319
+ }, [context.state?.[`${source}:${operation}:${key}`]]);
320
+
321
+ const dispatch = useCallback(
322
+ (state: NetworkState<T>) => {
323
+ context.dispatch({ key, operation, source, state });
324
+ },
325
+ [key, operation, source, context.dispatch]
326
+ );
327
+
328
+ const debounceDelay = useMemo(() => {
329
+ return requestDebounceDelay ?? context.configs?.debounceDelay ?? 0;
330
+ }, [context.configs, requestDebounceDelay]);
331
+
332
+ const execute = useCallback(
333
+ async (request: RequestType) => {
334
+ logger.info(`Executing request ${key} ${operation} ${source}`);
335
+ logger.debug(`⇨`, request)
336
+
337
+ const abortController = new AbortController();
338
+ setAbortController(abortController);
339
+
340
+ const requestConfig: RequestType = {
341
+ ...request,
342
+ onUploadProgress(event: AxiosProgressEvent) {
343
+ dispatch(
344
+ pending({
345
+ type: 'upload',
346
+ loaded: event.loaded,
347
+ total: event.total,
348
+ })
349
+ );
350
+ request.onUploadProgress?.(event);
351
+ },
352
+ onDownloadProgress(event: AxiosProgressEvent) {
353
+ dispatch(
354
+ pending({
355
+ type: 'download',
356
+ loaded: event.loaded,
357
+ total: event.total,
358
+ })
359
+ );
360
+ request.onDownloadProgress?.(event);
361
+ },
362
+ signal: abortController.signal,
363
+ };
364
+
365
+ await context.execute(requestConfig, dispatch, schema, errorSchema);
366
+ },
367
+ [networkState, context.dispatch, axios]
368
+ );
369
+
370
+ const deboundedExecute = useMemo(
371
+ () => debounce(execute, debounceDelay ?? 0),
372
+ [execute]
373
+ );
374
+
375
+ const clear = useCallback(() => {
376
+ logger.info(`Clearing request ${key} ${operation} ${source}`);
377
+ dispatch(init());
378
+ setAbortController((abortController) => {
379
+ logger.info(`Aborting request ${key} ${operation} ${source}`);
380
+ abortController?.abort();
381
+ return undefined;
382
+ });
383
+ }, [dispatch, abortController]);
384
+
385
+ return [networkState, deboundedExecute, clear, dispatch];
386
+ }
387
+
388
+ function debounce<T extends (...args: any[]) => void>(func: T, delay: number) {
389
+ let timeoutId: any;
390
+
391
+ return (...args: Parameters<T>) => {
392
+ if (timeoutId) {
393
+ clearTimeout(timeoutId);
394
+ }
395
+ timeoutId = setTimeout(() => {
396
+ func(...args);
397
+ }, delay);
398
+ };
399
+ }
400
+
401
+ /**
402
+ * Handles central error extraction from the provided context.
403
+ * It filters the state to retain error states and maps them to a structured error object with additional context information.
404
+ * @return {Object[]} An array of objects representing the error states with context information such as source, operation, and key.
405
+ */
406
+ export function useCentralError() {
407
+ const ctx = useContext(Context);
408
+
409
+ return useMemo(() => {
410
+ return Object.entries(ctx.filteredState)
411
+ .filter(([, state]) => isError(state))
412
+ .map(([k, state]) => {
413
+ const [source, operation, key] = k.split(':');
414
+ return {
415
+ ...(state as ErrorState<unknown>),
416
+ source,
417
+ operation,
418
+ key,
419
+ } satisfies ErrorWithContext;
420
+ });
421
+ }, [ctx.filteredState]);
422
+ }
423
+
424
+ /**
425
+ * Uses central pending state handling by aggregating pending states from context.
426
+ * It calculates the overall progress of pending states if any, or returns an initial state otherwise.
427
+ *
428
+ * @return {NetworkState} The aggregated network state based on the pending states and their progress.
429
+ */
430
+ export function useCentralPendingState() {
431
+ const ctx = useContext(Context);
432
+
433
+ const result: NetworkState = useMemo(() => {
434
+ const pendingStates = Object.values(ctx.filteredState).filter(isPending);
435
+ if (!pendingStates.length) {
436
+ return init();
437
+ }
438
+
439
+ const progress = pendingStates
440
+ .filter((a) => a.progress)
441
+ .reduce(
442
+ (progress, current) => {
443
+ return {
444
+ total: progress.total + (current.progress?.total ?? 0),
445
+ loaded: progress.loaded + (current.progress?.loaded ?? 0),
446
+ };
447
+ },
448
+ { total: 0, loaded: 0 } satisfies Progress
449
+ );
450
+ return pending(progress.total ? progress : undefined);
451
+ }, [ctx.filteredState]);
452
+
453
+ return result;
454
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,13 @@
1
+ import log from 'loglevel';
2
+
3
+ // Set the default logging level (can be overridden via environment variables)
4
+ log.setLevel(process.env.LOG_LEVEL as log.LogLevelDesc || 'error');
5
+
6
+ const logWrapper = {
7
+ info: (msg: string, meta?: object) => meta ? log.info(msg, meta) : log.info(msg),
8
+ warn: (msg: string, meta?: object) => meta ? log.warn(msg, meta) : log.warn(msg),
9
+ error: (msg: string, meta?: object) => meta ? log.error(msg, meta) : log.error(msg),
10
+ debug: (msg: string, meta?: object) => meta ? log.debug(msg, meta) : log.debug(msg),
11
+ };
12
+
13
+ export default logWrapper;
@@ -0,0 +1,184 @@
1
+ import { ZodSchema } from 'zod';
2
+ import { XMLParser } from 'fast-xml-parser';
3
+
4
+ type Encoders = {
5
+ [k: string]: <T>(request: T,
6
+ mediaType: string,
7
+ schema: ZodSchema) => Promise<any>;
8
+ };
9
+
10
+ const encoders: Encoders = {};
11
+
12
+ export function encode<T>(request: T, mediaType: string, schema: ZodSchema) {
13
+ if (encoders[mediaType]) {
14
+ return encoders[mediaType](request, mediaType, schema);
15
+ }
16
+ return request;
17
+ }
18
+
19
+ encoders['application/json'] = async (request) => {
20
+ return request;
21
+ }
22
+
23
+ function appendFormData(
24
+ formData: FormData,
25
+ data: any,
26
+ parentKey: string
27
+ ): void {
28
+ if (data instanceof Blob || typeof data === 'string') {
29
+ formData.append(parentKey, data);
30
+ } else if (data !== null && typeof data === 'object') {
31
+ if (Array.isArray(data)) {
32
+ data.forEach((item: any) => {
33
+ const key = `${parentKey}`;
34
+ appendFormData(formData, item, key);
35
+ });
36
+ } else {
37
+ Object.keys(data).forEach((key: string) => {
38
+ const newKey = parentKey ? `${parentKey}[${key}]` : key;
39
+ appendFormData(formData, data[key], newKey);
40
+ });
41
+ }
42
+ } else {
43
+ formData.append(parentKey, data == null ? '' : String(data));
44
+ }
45
+ }
46
+
47
+
48
+ encoders['multipart/form-data'] = async (request) => {
49
+ const _request = request as Record<string, any>;
50
+ const formData = new FormData();
51
+ Object.keys(_request).forEach((key: string) => {
52
+ appendFormData(formData, _request[key], key);
53
+ });
54
+ return formData;
55
+ }
56
+
57
+ encoders['application/octet-stream'] = async (request) => {
58
+ return request;
59
+ }
60
+
61
+ encoders['application/x-www-form-urlencoded'] = async (request) => {
62
+ const formData = new FormData();
63
+ for (const key in request) {
64
+ const value = request[key];
65
+ formData.append(key, value instanceof Blob || typeof value === 'string' ? value : String(value));
66
+ }
67
+ return formData;
68
+ }
69
+
70
+ type Transformers = {
71
+ [k: string]: <T>(
72
+ request: Request,
73
+ mediaType: string,
74
+ schema: ZodSchema
75
+ ) => Promise<T>;
76
+ };
77
+
78
+ const transformers: Transformers = {};
79
+
80
+ export function transform<T>(
81
+ request: Request,
82
+ mediaType: string,
83
+ schema: ZodSchema
84
+ ): Promise<T> {
85
+ if (transformers[mediaType]) {
86
+ return transformers[mediaType](request, mediaType, schema);
87
+ }
88
+ throw new Error(`Unsupported media type: ` + mediaType);
89
+ }
90
+
91
+ transformers['application/json'] = async (request, mediaType, schema) => {
92
+ return schema.parse(await request.json());
93
+ };
94
+
95
+ transformers['multipart/form-data'] = async (request, mediaType, schema) => {
96
+ const formData = await request.formData();
97
+ const content: Record<string, any> = {};
98
+ formData.forEach((value, key) => {
99
+ if (content[key]) {
100
+ if (!(content[key] instanceof Array)) {
101
+ content[key] = [content[key]];
102
+ }
103
+ content[key].push(value);
104
+ } else {
105
+ content[key] = value
106
+ }
107
+ });
108
+ return schema.parse(content);
109
+ };
110
+
111
+ transformers['application/octet-stream'] = async (
112
+ request,
113
+ mediaType,
114
+ schema
115
+ ) => {
116
+ return schema.parse(
117
+ new Blob([await request.arrayBuffer()], {
118
+ type: 'application/octet-stream',
119
+ })
120
+ );
121
+ };
122
+
123
+ transformers['application/x-www-form-urlencoded'] = async (
124
+ request,
125
+ mediaType,
126
+ schema
127
+ ) => {
128
+ const formData = await request.formData();
129
+ const content: Record<string, any> = {};
130
+ formData.forEach((value, key) => (content[key] = value));
131
+ return schema.parse(content);
132
+ };
133
+
134
+ transformers['application/xml'] = async (request, mediaType, schema) => {
135
+ const xmlParser = new XMLParser();
136
+ const content = await xmlParser.parse(await request.text());
137
+ return schema.parse(await content);
138
+ };
139
+
140
+ transformers['text/plain'] = async (request, mediaType, schema) => {
141
+ return schema.parse(await request.text());
142
+ };
143
+
144
+ transformers['text/html'] = async (request, mediaType, schema) => {
145
+ return schema.parse(await request.text());
146
+ };
147
+
148
+ transformers['text/css'] = async (request, mediaType, schema) => {
149
+ return schema.parse(await request.text());
150
+ };
151
+
152
+ transformers['text/javascript'] = async (request, mediaType, schema) => {
153
+ return schema.parse(await request.text());
154
+ };
155
+
156
+ type ResponseTransformers = {
157
+ [k: string]: <T>(
158
+ data: any,
159
+ mediaType: string,
160
+ schema: ZodSchema
161
+ ) => Promise<T>;
162
+ };
163
+
164
+ const responseTransformers: ResponseTransformers = {};
165
+
166
+ responseTransformers['application/json'] = async (data, mediaType, schema) => {
167
+ return schema.parse(data);
168
+ };
169
+
170
+ responseTransformers['application/xml'] = async (data, mediaType, schema) => {
171
+ const parsed = new XMLParser().parse(data);
172
+ return schema.parse(parsed);
173
+ }
174
+
175
+ export async function transformResponse<T>(
176
+ data: any,
177
+ mediaType: string,
178
+ schema: ZodSchema
179
+ ): Promise<T> {
180
+ if (responseTransformers[mediaType]) {
181
+ return await responseTransformers[mediaType](data, mediaType, schema);
182
+ }
183
+ return data
184
+ }