@openapi-qraft/react 1.0.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.
Files changed (107) hide show
  1. package/README.md +287 -0
  2. package/dist/QraftContext.d.ts +8 -0
  3. package/dist/QraftContext.d.ts.map +1 -0
  4. package/dist/QraftContext.js +3 -0
  5. package/dist/QraftContext.js.map +1 -0
  6. package/dist/RequestClient.d.ts +19 -0
  7. package/dist/RequestClient.d.ts.map +1 -0
  8. package/dist/RequestClient.js +2 -0
  9. package/dist/RequestClient.js.map +1 -0
  10. package/dist/ServiceOperation.d.ts +122 -0
  11. package/dist/ServiceOperation.d.ts.map +1 -0
  12. package/dist/ServiceOperation.js +2 -0
  13. package/dist/ServiceOperation.js.map +1 -0
  14. package/dist/callbacks/getInfiniteQueryData.d.ts +6 -0
  15. package/dist/callbacks/getInfiniteQueryData.d.ts.map +1 -0
  16. package/dist/callbacks/getInfiniteQueryData.js +8 -0
  17. package/dist/callbacks/getInfiniteQueryData.js.map +1 -0
  18. package/dist/callbacks/getInfiniteQueryKey.d.ts +8 -0
  19. package/dist/callbacks/getInfiniteQueryKey.d.ts.map +1 -0
  20. package/dist/callbacks/getInfiniteQueryKey.js +7 -0
  21. package/dist/callbacks/getInfiniteQueryKey.js.map +1 -0
  22. package/dist/callbacks/getMutationKey.d.ts +5 -0
  23. package/dist/callbacks/getMutationKey.d.ts.map +1 -0
  24. package/dist/callbacks/getMutationKey.js +22 -0
  25. package/dist/callbacks/getMutationKey.js.map +1 -0
  26. package/dist/callbacks/getQueryData.d.ts +5 -0
  27. package/dist/callbacks/getQueryData.d.ts.map +1 -0
  28. package/dist/callbacks/getQueryData.js +8 -0
  29. package/dist/callbacks/getQueryData.js.map +1 -0
  30. package/dist/callbacks/getQueryKey.d.ts +7 -0
  31. package/dist/callbacks/getQueryKey.d.ts.map +1 -0
  32. package/dist/callbacks/getQueryKey.js +4 -0
  33. package/dist/callbacks/getQueryKey.js.map +1 -0
  34. package/dist/callbacks/mutationFn.d.ts +4 -0
  35. package/dist/callbacks/mutationFn.d.ts.map +1 -0
  36. package/dist/callbacks/mutationFn.js +8 -0
  37. package/dist/callbacks/mutationFn.js.map +1 -0
  38. package/dist/callbacks/queryFn.d.ts +15 -0
  39. package/dist/callbacks/queryFn.d.ts.map +1 -0
  40. package/dist/callbacks/queryFn.js +10 -0
  41. package/dist/callbacks/queryFn.js.map +1 -0
  42. package/dist/callbacks/setInfiniteQueryData.d.ts +6 -0
  43. package/dist/callbacks/setInfiniteQueryData.d.ts.map +1 -0
  44. package/dist/callbacks/setInfiniteQueryData.js +6 -0
  45. package/dist/callbacks/setInfiniteQueryData.js.map +1 -0
  46. package/dist/callbacks/setQueryData.d.ts +5 -0
  47. package/dist/callbacks/setQueryData.d.ts.map +1 -0
  48. package/dist/callbacks/setQueryData.js +9 -0
  49. package/dist/callbacks/setQueryData.js.map +1 -0
  50. package/dist/callbacks/useInfiniteQuery.d.ts +13 -0
  51. package/dist/callbacks/useInfiniteQuery.d.ts.map +1 -0
  52. package/dist/callbacks/useInfiniteQuery.js +50 -0
  53. package/dist/callbacks/useInfiniteQuery.js.map +1 -0
  54. package/dist/callbacks/useMutation.d.ts +7 -0
  55. package/dist/callbacks/useMutation.d.ts.map +1 -0
  56. package/dist/callbacks/useMutation.js +51 -0
  57. package/dist/callbacks/useMutation.js.map +1 -0
  58. package/dist/callbacks/useQuery.d.ts +9 -0
  59. package/dist/callbacks/useQuery.d.ts.map +1 -0
  60. package/dist/callbacks/useQuery.js +27 -0
  61. package/dist/callbacks/useQuery.js.map +1 -0
  62. package/dist/index.d.ts +9 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +4 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/lib/createCallbackProxyDecoration.d.ts +5 -0
  67. package/dist/lib/createCallbackProxyDecoration.d.ts.map +1 -0
  68. package/dist/lib/createCallbackProxyDecoration.js +21 -0
  69. package/dist/lib/createCallbackProxyDecoration.js.map +1 -0
  70. package/dist/lib/createRecursiveProxy.d.ts +7 -0
  71. package/dist/lib/createRecursiveProxy.d.ts.map +1 -0
  72. package/dist/lib/createRecursiveProxy.js +24 -0
  73. package/dist/lib/createRecursiveProxy.js.map +1 -0
  74. package/dist/lib/request.d.ts +32 -0
  75. package/dist/lib/request.d.ts.map +1 -0
  76. package/dist/lib/request.js +167 -0
  77. package/dist/lib/request.js.map +1 -0
  78. package/dist/qraftAPIClient.d.ts +25 -0
  79. package/dist/qraftAPIClient.d.ts.map +1 -0
  80. package/dist/qraftAPIClient.js +22 -0
  81. package/dist/qraftAPIClient.js.map +1 -0
  82. package/package.json +79 -0
  83. package/src/QraftContext.ts +12 -0
  84. package/src/RequestClient.ts +32 -0
  85. package/src/ServiceOperation.ts +306 -0
  86. package/src/callbacks/getInfiniteQueryData.ts +26 -0
  87. package/src/callbacks/getInfiniteQueryKey.ts +23 -0
  88. package/src/callbacks/getMutationKey.ts +47 -0
  89. package/src/callbacks/getQueryData.ts +20 -0
  90. package/src/callbacks/getQueryKey.ts +19 -0
  91. package/src/callbacks/mutationFn.ts +22 -0
  92. package/src/callbacks/queryFn.ts +21 -0
  93. package/src/callbacks/setInfiniteQueryData.ts +29 -0
  94. package/src/callbacks/setQueryData.ts +23 -0
  95. package/src/callbacks/shelfMerge.test.ts +94 -0
  96. package/src/callbacks/useInfiniteQuery.ts +88 -0
  97. package/src/callbacks/useMutation.ts +100 -0
  98. package/src/callbacks/useQuery.ts +56 -0
  99. package/src/index.ts +11 -0
  100. package/src/lib/createCallbackProxyDecoration.ts +34 -0
  101. package/src/lib/createRecursiveProxy.ts +27 -0
  102. package/src/lib/request.ts +252 -0
  103. package/src/mocks/handlers.ts +252 -0
  104. package/src/mocks/node.ts +5 -0
  105. package/src/qraftAPIClient.test.tsx +595 -0
  106. package/src/qraftAPIClient.ts +77 -0
  107. package/src/setupTests.ts +17 -0
@@ -0,0 +1,595 @@
1
+ import React, { createContext, ReactNode } from 'react';
2
+
3
+ import { QraftContext as QraftContextDist } from '@openapi-qraft/react';
4
+ import {
5
+ QueryClient,
6
+ QueryClientProvider,
7
+ useQueryClient,
8
+ } from '@tanstack/react-query';
9
+ import { act, renderHook, waitFor } from '@testing-library/react';
10
+
11
+ import { createAPIClient } from './fixtures/api/index.js';
12
+ import { bodySerializer, urlSerializer, request } from './lib/request.js';
13
+ import { QraftContext, QraftContextValue } from './QraftContext.js';
14
+ import type { RequestClient } from './RequestClient.js';
15
+
16
+ const qraft = createAPIClient();
17
+
18
+ const requestClient: RequestClient = async (schema, options) => {
19
+ return request(
20
+ {
21
+ baseUrl: 'https://api.sandbox.monite.com/v1',
22
+ },
23
+ { ...schema, ...options },
24
+ { urlSerializer, bodySerializer }
25
+ );
26
+ };
27
+
28
+ describe('Qraft uses Queries', () => {
29
+ it('supports useQuery', async () => {
30
+ const { result } = renderHook(
31
+ () =>
32
+ qraft.approvalPolicies.getApprovalPoliciesId.useQuery({
33
+ header: {
34
+ 'x-monite-version': '1.0.0',
35
+ },
36
+ path: {
37
+ approval_policy_id: '1',
38
+ },
39
+ query: {
40
+ items_order: ['asc', 'desc'],
41
+ },
42
+ }),
43
+ {
44
+ wrapper: Providers,
45
+ }
46
+ );
47
+
48
+ await waitFor(() => {
49
+ expect(result.current.data).toEqual({
50
+ header: {
51
+ 'x-monite-version': '1.0.0',
52
+ },
53
+ path: {
54
+ approval_policy_id: '1',
55
+ },
56
+ query: {
57
+ items_order: ['asc', 'desc'],
58
+ },
59
+ });
60
+ });
61
+ });
62
+
63
+ it('supports custom context', async () => {
64
+ const QraftCustomContext = createContext<QraftContextValue>(undefined);
65
+
66
+ const customQraft = createAPIClient({ context: QraftCustomContext });
67
+
68
+ const { result } = renderHook(
69
+ () =>
70
+ customQraft.approvalPolicies.getApprovalPoliciesId.useQuery({
71
+ header: {
72
+ 'x-monite-version': '1.0.0',
73
+ },
74
+ path: {
75
+ approval_policy_id: '1',
76
+ },
77
+ query: {
78
+ items_order: ['asc', 'desc'],
79
+ },
80
+ }),
81
+ {
82
+ wrapper: (props) => (
83
+ <QraftCustomContext.Provider
84
+ value={{
85
+ requestClient(schema, options) {
86
+ return request(
87
+ {
88
+ baseUrl: 'https://api.sandbox.monite.com/v1',
89
+ },
90
+ {
91
+ headers: { 'x-custom-provider': 'true' },
92
+ ...schema,
93
+ ...options,
94
+ },
95
+ { urlSerializer, bodySerializer }
96
+ );
97
+ },
98
+ }}
99
+ >
100
+ <Providers {...props} />
101
+ </QraftCustomContext.Provider>
102
+ ),
103
+ }
104
+ );
105
+
106
+ await waitFor(() => {
107
+ expect(result.current.data).toEqual({
108
+ header: {
109
+ 'x-monite-version': '1.0.0',
110
+ 'x-custom-provider': 'true',
111
+ },
112
+ path: {
113
+ approval_policy_id: '1',
114
+ },
115
+ query: {
116
+ items_order: ['asc', 'desc'],
117
+ },
118
+ });
119
+ });
120
+ });
121
+
122
+ it('supports useQuery with optional params', async () => {
123
+ const { result } = renderHook(() => qraft.files.getFileList.useQuery({}), {
124
+ wrapper: Providers,
125
+ });
126
+
127
+ await waitFor(() => {
128
+ expect(result.current.data).toEqual(undefined);
129
+ });
130
+ });
131
+ });
132
+
133
+ describe('Qraft uses Infinite Queries', () => {
134
+ it('supports useInfiniteQuery', async () => {
135
+ const { result } = renderHook(
136
+ () =>
137
+ qraft.files.getFiles.useInfiniteQuery(
138
+ {
139
+ header: {
140
+ 'x-monite-version': '1.0.0',
141
+ },
142
+ query: {
143
+ id__in: ['1', '2'],
144
+ },
145
+ },
146
+ {
147
+ getNextPageParam: (lastPage, allPages, params) => {
148
+ return {
149
+ query: {
150
+ page: params.query?.page
151
+ ? String(Number(params.query.page) + 1)
152
+ : undefined,
153
+ },
154
+ };
155
+ },
156
+ initialPageParam: {
157
+ query: {
158
+ page: '1',
159
+ },
160
+ },
161
+ }
162
+ ),
163
+ {
164
+ wrapper: Providers,
165
+ }
166
+ );
167
+
168
+ await waitFor(() => {
169
+ expect(result.current.data).toEqual({
170
+ pageParams: [
171
+ {
172
+ query: {
173
+ page: '1',
174
+ },
175
+ },
176
+ ],
177
+ pages: [
178
+ {
179
+ header: {
180
+ 'x-monite-version': '1.0.0',
181
+ },
182
+ query: {
183
+ id__in: ['1', '2'],
184
+ page: '1',
185
+ },
186
+ },
187
+ ],
188
+ });
189
+ });
190
+ });
191
+
192
+ it('supports useInfiniteQuery with next page', async () => {
193
+ const queryClient = new QueryClient();
194
+
195
+ const { result } = renderHook(
196
+ () =>
197
+ qraft.files.getFiles.useInfiniteQuery(
198
+ {
199
+ header: {
200
+ 'x-monite-version': '1.0.0',
201
+ },
202
+ query: {
203
+ id__in: ['1', '2'],
204
+ },
205
+ },
206
+ {
207
+ getNextPageParam: (lastPage, allPages, params) => {
208
+ return {
209
+ query: {
210
+ page: params.query?.page
211
+ ? String(Number(params.query.page) + 1)
212
+ : undefined,
213
+ },
214
+ };
215
+ },
216
+ initialPageParam: {
217
+ query: {
218
+ page: '1',
219
+ },
220
+ },
221
+ }
222
+ ),
223
+ {
224
+ wrapper: (props) => <Providers {...props} queryClient={queryClient} />,
225
+ }
226
+ );
227
+
228
+ await waitFor(() => {
229
+ expect(result.current.isSuccess).toBeTruthy();
230
+ });
231
+
232
+ await act(async () => {
233
+ await result.current.fetchNextPage();
234
+ });
235
+
236
+ const { result: result2 } = renderHook(
237
+ () => {
238
+ const queryClient = useQueryClient();
239
+
240
+ return queryClient.getQueryData(
241
+ qraft.files.getFiles.getInfiniteQueryKey({
242
+ header: {
243
+ 'x-monite-version': '1.0.0',
244
+ },
245
+ query: {
246
+ id__in: ['1', '2'],
247
+ },
248
+ })
249
+ );
250
+ },
251
+ {
252
+ wrapper: (props) => <Providers {...props} queryClient={queryClient} />,
253
+ }
254
+ );
255
+
256
+ await waitFor(() => {
257
+ expect(result2.current).toEqual({
258
+ pageParams: [
259
+ {
260
+ query: {
261
+ page: '1',
262
+ },
263
+ },
264
+ {
265
+ query: {
266
+ page: '2',
267
+ },
268
+ },
269
+ ],
270
+ pages: [
271
+ {
272
+ header: {
273
+ 'x-monite-version': '1.0.0',
274
+ },
275
+ query: {
276
+ id__in: ['1', '2'],
277
+ page: '1',
278
+ },
279
+ },
280
+ {
281
+ header: {
282
+ 'x-monite-version': '1.0.0',
283
+ },
284
+ query: {
285
+ id__in: ['1', '2'],
286
+ page: '2',
287
+ },
288
+ },
289
+ ],
290
+ });
291
+ });
292
+ });
293
+ });
294
+
295
+ describe('Qraft uses Mutations', () => {
296
+ it('supports useMutation without predefined parameters', async () => {
297
+ const { result } = renderHook(
298
+ () => qraft.entities.postEntitiesIdDocuments.useMutation(),
299
+ {
300
+ wrapper: Providers,
301
+ }
302
+ );
303
+
304
+ act(() => {
305
+ result.current.mutate({
306
+ header: {
307
+ 'x-monite-version': '1.0.0',
308
+ },
309
+ path: {
310
+ entity_id: '1',
311
+ },
312
+ query: {
313
+ referer: 'https://example.com',
314
+ },
315
+ body: {
316
+ verification_document_back: 'back',
317
+ verification_document_front: 'front',
318
+ },
319
+ });
320
+ });
321
+
322
+ await waitFor(() => {
323
+ expect(result.current.data).toEqual({
324
+ header: {
325
+ 'x-monite-version': '1.0.0',
326
+ },
327
+ path: {
328
+ entity_id: '1',
329
+ },
330
+ query: {
331
+ referer: 'https://example.com',
332
+ },
333
+ body: {
334
+ verification_document_back: 'back',
335
+ verification_document_front: 'front',
336
+ },
337
+ });
338
+ });
339
+ });
340
+
341
+ it('supports useMutation with predefined parameters', async () => {
342
+ const { result } = renderHook(
343
+ () =>
344
+ qraft.entities.postEntitiesIdDocuments.useMutation({
345
+ header: {
346
+ 'x-monite-version': '1.0.0',
347
+ },
348
+ path: {
349
+ entity_id: '1',
350
+ },
351
+ query: {
352
+ referer: 'https://example.com',
353
+ },
354
+ }),
355
+ {
356
+ wrapper: Providers,
357
+ }
358
+ );
359
+
360
+ act(() => {
361
+ result.current.mutate({
362
+ verification_document_back: 'back',
363
+ verification_document_front: 'front',
364
+ });
365
+ });
366
+
367
+ await waitFor(() => {
368
+ expect(result.current.data).toEqual({
369
+ header: {
370
+ 'x-monite-version': '1.0.0',
371
+ },
372
+ path: {
373
+ entity_id: '1',
374
+ },
375
+ query: {
376
+ referer: 'https://example.com',
377
+ },
378
+ body: {
379
+ verification_document_back: 'back',
380
+ verification_document_front: 'front',
381
+ },
382
+ });
383
+ });
384
+ });
385
+
386
+ it('supports useMutation with form data', async () => {
387
+ const { result } = renderHook(() => qraft.files.postFiles.useMutation(), {
388
+ wrapper: Providers,
389
+ });
390
+
391
+ act(() => {
392
+ result.current.mutate({
393
+ body: {
394
+ file: new File([''], 'file.png'),
395
+ file_description: 'my file',
396
+ },
397
+ });
398
+ });
399
+
400
+ await waitFor(() => {
401
+ expect(result.current.data).toEqual({
402
+ body: {
403
+ file: 'file.png',
404
+ file_description: 'my file',
405
+ },
406
+ });
407
+ });
408
+ });
409
+ });
410
+
411
+ describe('Qraft uses Query Function', () => {
412
+ it('uses queryFn', async () => {
413
+ const result = await qraft.approvalPolicies.getApprovalPoliciesId.queryFn(
414
+ {
415
+ parameters: {
416
+ header: {
417
+ 'x-monite-version': '1.0.0',
418
+ },
419
+ path: {
420
+ approval_policy_id: '1',
421
+ },
422
+ query: {
423
+ items_order: ['asc', 'desc'],
424
+ },
425
+ },
426
+ },
427
+ requestClient
428
+ );
429
+
430
+ expect(result).toEqual({
431
+ header: {
432
+ 'x-monite-version': '1.0.0',
433
+ },
434
+ path: {
435
+ approval_policy_id: '1',
436
+ },
437
+ query: {
438
+ items_order: ['asc', 'desc'],
439
+ },
440
+ });
441
+ });
442
+ });
443
+
444
+ describe('Qraft uses utils', () => {
445
+ it('returns _def', () => {
446
+ // @ts-ignore
447
+ expect(qraft.counterparts.postCounterpartsIdAddresses._def()).toEqual({
448
+ path: ['counterparts', 'postCounterpartsIdAddresses'],
449
+ });
450
+ });
451
+
452
+ it('throws an error when calling an unsupported method ', () => {
453
+ expect(() =>
454
+ // @ts-ignore
455
+ qraft.counterparts.postCounterpartsIdAddresses.unsupportedMethod()
456
+ ).toThrowError(/Function unsupportedMethod is not supported/i);
457
+ });
458
+ });
459
+
460
+ describe('Qraft uses setQueryData', () => {
461
+ it('uses setQueryData & getQueryData', async () => {
462
+ const queryClient = new QueryClient();
463
+
464
+ qraft.files.getFiles.setQueryData(
465
+ {
466
+ header: {
467
+ 'x-monite-version': '1.0.0',
468
+ },
469
+ query: {
470
+ id__in: ['1', '2'],
471
+ },
472
+ },
473
+ {
474
+ header: {
475
+ 'x-monite-version': '1.0.0',
476
+ },
477
+ query: {
478
+ id__in: ['1', '2'],
479
+ },
480
+ },
481
+ queryClient
482
+ );
483
+
484
+ expect(
485
+ qraft.files.getFiles.getQueryData(
486
+ {
487
+ header: {
488
+ 'x-monite-version': '1.0.0',
489
+ },
490
+ query: {
491
+ id__in: ['1', '2'],
492
+ },
493
+ },
494
+ queryClient
495
+ )
496
+ ).toEqual({
497
+ header: {
498
+ 'x-monite-version': '1.0.0',
499
+ },
500
+ query: {
501
+ id__in: ['1', '2'],
502
+ },
503
+ });
504
+ });
505
+ });
506
+
507
+ describe('Qraft uses setInfiniteQueryData', () => {
508
+ it('uses setInfiniteQueryData & getInfiniteQueryData', async () => {
509
+ const queryClient = new QueryClient();
510
+
511
+ qraft.files.getFiles.setInfiniteQueryData(
512
+ {
513
+ header: {
514
+ 'x-monite-version': '1.0.0',
515
+ },
516
+ query: {
517
+ id__in: ['1', '2'],
518
+ },
519
+ },
520
+ {
521
+ pages: [
522
+ {
523
+ header: {
524
+ 'x-monite-version': '1.0.0',
525
+ },
526
+ query: {
527
+ id__in: ['1', '2'],
528
+ },
529
+ },
530
+ ],
531
+ pageParams: [
532
+ {
533
+ query: {
534
+ page: '1',
535
+ },
536
+ },
537
+ ],
538
+ },
539
+ queryClient
540
+ );
541
+
542
+ expect(
543
+ qraft.files.getFiles.getInfiniteQueryData(
544
+ {
545
+ header: {
546
+ 'x-monite-version': '1.0.0',
547
+ },
548
+ query: {
549
+ id__in: ['1', '2'],
550
+ },
551
+ },
552
+ queryClient
553
+ )
554
+ ).toEqual({
555
+ pages: [
556
+ {
557
+ header: {
558
+ 'x-monite-version': '1.0.0',
559
+ },
560
+ query: {
561
+ id__in: ['1', '2'],
562
+ },
563
+ },
564
+ ],
565
+ pageParams: [
566
+ {
567
+ query: {
568
+ page: '1',
569
+ },
570
+ },
571
+ ],
572
+ });
573
+ });
574
+ });
575
+
576
+ function Providers({
577
+ children,
578
+ queryClient,
579
+ }: {
580
+ children: ReactNode;
581
+ queryClient?: QueryClient;
582
+ }) {
583
+ queryClient = React.useState(() => queryClient ?? new QueryClient())[0];
584
+
585
+ return (
586
+ <QueryClientProvider client={queryClient}>
587
+ {/* We should use precompiled `QraftContextDist`,
588
+ * because callbacks are imported from `@openapi-qraft` package `/dist` folder
589
+ */}
590
+ <QraftContextDist.Provider value={{ requestClient }}>
591
+ {children}
592
+ </QraftContextDist.Provider>
593
+ </QueryClientProvider>
594
+ );
595
+ }
@@ -0,0 +1,77 @@
1
+ import { Context } from 'react';
2
+
3
+ import { createCallbackProxyDecoration } from './lib/createCallbackProxyDecoration.js';
4
+ import { QraftContextValue } from './QraftContext.js';
5
+ import type { RequestClientSchema } from './RequestClient.js';
6
+
7
+ export type QraftClientOptions = {
8
+ context?: Context<QraftContextValue>;
9
+ };
10
+
11
+ export const qraftAPIClient = <
12
+ Services extends {
13
+ [service in keyof Services]: {
14
+ [method in keyof Services[service]]: { schema: RequestClientSchema };
15
+ };
16
+ },
17
+ Callbacks extends Record<string, (...rest: any[]) => any>,
18
+ >(
19
+ services: {
20
+ [service in keyof Services]: {
21
+ [method in keyof Services[service]]: {
22
+ schema: Services[service][method]['schema'];
23
+ };
24
+ };
25
+ },
26
+ callbacks: Callbacks,
27
+ options?: QraftClientOptions
28
+ ): ServicesCallbacksFilter<Services, keyof Callbacks> => {
29
+ return createCallbackProxyDecoration(
30
+ Object.keys(callbacks),
31
+ (path, functionName, args) => {
32
+ const serviceOperation = getByPath(services, path);
33
+ if (!isServiceOperation(serviceOperation))
34
+ throw new Error(`Service operation not found: ${path.join('.')}`);
35
+
36
+ if (functionName in callbacks) {
37
+ return callbacks[functionName as keyof Callbacks](
38
+ options,
39
+ serviceOperation.schema,
40
+ args
41
+ );
42
+ }
43
+
44
+ throw new Error(`Not supported API method: ${String(functionName)}`);
45
+ }
46
+ ) as never;
47
+ };
48
+
49
+ function isServiceOperation(
50
+ input: unknown
51
+ ): input is { schema: RequestClientSchema } {
52
+ return input !== null && typeof input === 'object' && 'schema' in input;
53
+ }
54
+
55
+ function getByPath(obj: Record<string, unknown>, path: string[]) {
56
+ return path.reduce<unknown>((acc, key) => {
57
+ if (acc && typeof acc === 'object' && key in acc)
58
+ return acc[key as keyof typeof acc];
59
+ }, obj);
60
+ }
61
+
62
+ type ServicesCallbacksFilter<Services, Callbacks> = Services extends {
63
+ [serviceName in keyof Services]: {
64
+ [method in keyof Services[serviceName]]: { schema: RequestClientSchema };
65
+ };
66
+ }
67
+ ? {
68
+ [serviceName in keyof Services]: {
69
+ [method in keyof Services[serviceName]]: Pick<
70
+ Services[serviceName][method],
71
+ FilterKeys<keyof Services[serviceName][method], Callbacks | 'schema'>
72
+ >;
73
+ };
74
+ }
75
+ : never;
76
+
77
+ type FilterKeys<T, K> = K extends T ? K : never;
@@ -0,0 +1,17 @@
1
+ import '@testing-library/jest-dom';
2
+
3
+ import { afterAll, afterEach, beforeAll } from 'vitest';
4
+
5
+ import { server } from './mocks/node.js';
6
+
7
+ beforeAll(() => {
8
+ server.listen();
9
+ });
10
+
11
+ afterEach(() => {
12
+ server.resetHandlers();
13
+ });
14
+
15
+ afterAll(() => {
16
+ server.close();
17
+ });