@openapi-qraft/react 2.8.0 → 2.10.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 (37) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/cjs/callbacks/useIsFetching.cjs +1 -1
  3. package/dist/cjs/callbacks/useIsFetching.cjs.map +1 -1
  4. package/dist/cjs/callbacks/useIsMutating.cjs +1 -1
  5. package/dist/cjs/callbacks/useIsMutating.cjs.map +1 -1
  6. package/dist/cjs/callbacks/useMutationState.cjs +1 -1
  7. package/dist/cjs/callbacks/useMutationState.cjs.map +1 -1
  8. package/dist/cjs/lib/requestFn.cjs +11 -24
  9. package/dist/cjs/lib/requestFn.cjs.map +1 -1
  10. package/dist/cjs/qraftAPIClient.cjs +1 -1
  11. package/dist/cjs/qraftAPIClient.cjs.map +1 -1
  12. package/dist/esm/callbacks/useIsFetching.js +1 -1
  13. package/dist/esm/callbacks/useIsFetching.js.map +1 -1
  14. package/dist/esm/callbacks/useIsMutating.js +1 -1
  15. package/dist/esm/callbacks/useIsMutating.js.map +1 -1
  16. package/dist/esm/callbacks/useMutationState.js +1 -1
  17. package/dist/esm/callbacks/useMutationState.js.map +1 -1
  18. package/dist/esm/lib/requestFn.js +11 -24
  19. package/dist/esm/lib/requestFn.js.map +1 -1
  20. package/dist/esm/qraftAPIClient.js +1 -1
  21. package/dist/esm/qraftAPIClient.js.map +1 -1
  22. package/dist/types/callbacks/useIsFetching.d.ts +1 -1
  23. package/dist/types/callbacks/useIsFetching.d.ts.map +1 -1
  24. package/dist/types/callbacks/useIsMutating.d.ts +1 -1
  25. package/dist/types/callbacks/useIsMutating.d.ts.map +1 -1
  26. package/dist/types/callbacks/useMutationState.d.ts +1 -1
  27. package/dist/types/callbacks/useMutationState.d.ts.map +1 -1
  28. package/dist/types/lib/requestFn.d.ts.map +1 -1
  29. package/dist/types/qraftAPIClient.d.ts +39 -12
  30. package/dist/types/qraftAPIClient.d.ts.map +1 -1
  31. package/package.json +5 -5
  32. package/src/callbacks/useIsFetching.ts +2 -2
  33. package/src/callbacks/useIsMutating.ts +2 -2
  34. package/src/callbacks/useMutationState.ts +2 -2
  35. package/src/lib/requestFn.ts +15 -28
  36. package/src/lib/urlSerializer.test.ts +265 -0
  37. package/src/qraftAPIClient.ts +100 -35
@@ -14,6 +14,38 @@ describe('urlSerializer', () => {
14
14
  ).toBe('https://api.example.com/users/123/posts/456');
15
15
  });
16
16
 
17
+ it('should correctly encode complex characters in path and query parameters', () => {
18
+ const info = {
19
+ baseUrl: 'https://api.example.com',
20
+ parameters: {
21
+ path: {
22
+ query: 'hello/world:test@domain.com',
23
+ category: 'books & magazines',
24
+ },
25
+ query: {
26
+ filter: 'price:$10-$50',
27
+ tags: ['tag/with/slash', 'tag:with:colon'],
28
+ special: 'symbols!@#$%^&*()+={}[]|\\:";\'<>?,./~`',
29
+ },
30
+ },
31
+ } as const;
32
+
33
+ const result = urlSerializer(
34
+ { url: '/search/{query}/category/{category}', method: 'get' },
35
+ info
36
+ );
37
+
38
+ const expectedUrl = new URL(
39
+ 'https://api.example.com/search/hello%2Fworld%3Atest%40domain.com/category/books%20%26%20magazines'
40
+ );
41
+ expectedUrl.searchParams.set('filter', info.parameters.query.filter);
42
+ expectedUrl.searchParams.append('tags', info.parameters.query.tags[0]);
43
+ expectedUrl.searchParams.append('tags', info.parameters.query.tags[1]);
44
+ expectedUrl.searchParams.set('special', info.parameters.query.special);
45
+
46
+ expect(result).toBe(expectedUrl.toString());
47
+ });
48
+
17
49
  it('should correctly append query parameters', () => {
18
50
  expect(
19
51
  urlSerializer(
@@ -215,4 +247,237 @@ describe('urlSerializer', () => {
215
247
  )
216
248
  ).toBe('https://api.example.com/users/undefined/posts/456');
217
249
  });
250
+
251
+ it('should correctly serialize query parameters with Date objects', () => {
252
+ expect(
253
+ urlSerializer(
254
+ { url: '/events', method: 'get' },
255
+ {
256
+ baseUrl: 'https://api.example.com',
257
+ parameters: {
258
+ query: {
259
+ startDate: new Date('2023-12-25T10:30:00.000Z'),
260
+ limit: 5,
261
+ },
262
+ },
263
+ }
264
+ )
265
+ ).toBe(
266
+ `https://api.example.com/events?startDate=2023-12-25T10%3A30%3A00.000Z&limit=5`
267
+ );
268
+ });
269
+
270
+ it('should correctly serialize query parameters with Date objects in arrays', () => {
271
+ expect(
272
+ urlSerializer(
273
+ { url: '/events', method: 'get' },
274
+ {
275
+ baseUrl: 'https://api.example.com',
276
+ parameters: {
277
+ query: {
278
+ dates: [
279
+ new Date('2023-01-01T00:00:00.000Z'),
280
+ new Date('2023-12-31T23:59:59.999Z'),
281
+ ],
282
+ active: true,
283
+ },
284
+ },
285
+ }
286
+ )
287
+ ).toBe(
288
+ 'https://api.example.com/events?dates=2023-01-01T00%3A00%3A00.000Z&dates=2023-12-31T23%3A59%3A59.999Z&active=true'
289
+ );
290
+ });
291
+
292
+ it('should correctly serialize query parameters with Date objects in nested objects', () => {
293
+ const startDate = new Date('2023-01-01T00:00:00.000Z');
294
+ expect(
295
+ urlSerializer(
296
+ { url: '/events', method: 'get' },
297
+ {
298
+ baseUrl: 'https://api.example.com',
299
+ parameters: {
300
+ query: {
301
+ timeRange: {
302
+ start: startDate,
303
+ end: null,
304
+ },
305
+ limit: 10,
306
+ },
307
+ },
308
+ }
309
+ )
310
+ ).toBe(
311
+ 'https://api.example.com/events?timeRange%5Bstart%5D=2023-01-01T00%3A00%3A00.000Z&limit=10'
312
+ );
313
+ });
314
+
315
+ it('should correctly serialize query parameters with boolean values', () => {
316
+ expect(
317
+ urlSerializer(
318
+ { url: '/users', method: 'get' },
319
+ {
320
+ baseUrl: 'https://api.example.com',
321
+ parameters: {
322
+ query: {
323
+ active: true,
324
+ verified: false,
325
+ premium: null,
326
+ limit: 10,
327
+ },
328
+ },
329
+ }
330
+ )
331
+ ).toBe('https://api.example.com/users?active=true&verified=false&limit=10');
332
+ });
333
+
334
+ it('should correctly serialize query parameters with numeric values', () => {
335
+ expect(
336
+ urlSerializer(
337
+ { url: '/products', method: 'get' },
338
+ {
339
+ baseUrl: 'https://api.example.com',
340
+ parameters: {
341
+ query: {
342
+ price: 0,
343
+ discount: 25.5,
344
+ quantity: -1,
345
+ maxPrice: 1000,
346
+ },
347
+ },
348
+ }
349
+ )
350
+ ).toBe(
351
+ 'https://api.example.com/products?price=0&discount=25.5&quantity=-1&maxPrice=1000'
352
+ );
353
+ });
354
+
355
+ it('should correctly serialize query parameters with special numeric values', () => {
356
+ expect(
357
+ urlSerializer(
358
+ { url: '/data', method: 'get' },
359
+ {
360
+ baseUrl: 'https://api.example.com',
361
+ parameters: {
362
+ query: {
363
+ infinity: Infinity,
364
+ negativeInfinity: -Infinity,
365
+ notANumber: NaN,
366
+ normal: 42,
367
+ },
368
+ },
369
+ }
370
+ )
371
+ ).toBe(
372
+ 'https://api.example.com/data?infinity=Infinity&negativeInfinity=-Infinity&notANumber=NaN&normal=42'
373
+ );
374
+ });
375
+
376
+ it('should correctly serialize query parameters with empty string values', () => {
377
+ expect(
378
+ urlSerializer(
379
+ { url: '/search', method: 'get' },
380
+ {
381
+ baseUrl: 'https://api.example.com',
382
+ parameters: {
383
+ query: {
384
+ query: '',
385
+ category: 'books',
386
+ sort: null,
387
+ },
388
+ },
389
+ }
390
+ )
391
+ ).toBe('https://api.example.com/search?query=&category=books');
392
+ });
393
+
394
+ it('should correctly serialize deeply nested objects', () => {
395
+ expect(
396
+ urlSerializer(
397
+ { url: '/complex', method: 'get' },
398
+ {
399
+ baseUrl: 'https://api.example.com',
400
+ parameters: {
401
+ query: {
402
+ level1: {
403
+ level2: {
404
+ level3: {
405
+ value: 'deep',
406
+ number: 42,
407
+ },
408
+ array: ['a', 'b'],
409
+ },
410
+ },
411
+ simple: 'value',
412
+ },
413
+ },
414
+ }
415
+ )
416
+ ).toBe(
417
+ 'https://api.example.com/complex?level1%5Blevel2%5D%5Blevel3%5D%5Bvalue%5D=deep&level1%5Blevel2%5D%5Blevel3%5D%5Bnumber%5D=42&level1%5Blevel2%5D%5Barray%5D=a&level1%5Blevel2%5D%5Barray%5D=b&simple=value'
418
+ );
419
+ });
420
+
421
+ it('should correctly serialize mixed arrays with different types', () => {
422
+ const testDate = new Date('2023-06-15T12:00:00.000Z');
423
+ expect(
424
+ urlSerializer(
425
+ { url: '/mixed', method: 'get' },
426
+ {
427
+ baseUrl: 'https://api.example.com',
428
+ parameters: {
429
+ query: {
430
+ mixed: ['string', 123, true, testDate, null, undefined],
431
+ clean: 'value',
432
+ },
433
+ },
434
+ }
435
+ )
436
+ ).toBe(
437
+ 'https://api.example.com/mixed?mixed=string&mixed=123&mixed=true&mixed=2023-06-15T12%3A00%3A00.000Z&clean=value'
438
+ );
439
+ });
440
+
441
+ it('should handle objects with arrays containing objects', () => {
442
+ expect(
443
+ urlSerializer(
444
+ { url: '/nested-array-objects', method: 'get' },
445
+ {
446
+ baseUrl: 'https://api.example.com',
447
+ parameters: {
448
+ query: {
449
+ items: [
450
+ { name: 'item1', value: 100 },
451
+ { name: 'item2', value: null },
452
+ ],
453
+ total: 2,
454
+ },
455
+ },
456
+ }
457
+ )
458
+ ).toBe(
459
+ 'https://api.example.com/nested-array-objects?items%5Bname%5D=item1&items%5Bvalue%5D=100&items%5Bname%5D=item2&total=2'
460
+ );
461
+ });
462
+
463
+ it('should handle edge cases with String() conversion', () => {
464
+ // Test to verify correct processing of different types of data
465
+ expect(
466
+ urlSerializer(
467
+ { url: '/edge-cases', method: 'get' },
468
+ {
469
+ baseUrl: 'https://api.example.com',
470
+ parameters: {
471
+ query: {
472
+ bigint: BigInt(123),
473
+ symbol: 'symbol-as-string', // Symbol cannot be serialized directly
474
+ func: '[Function]', // Functions should not get into query, but if they do - as a string
475
+ },
476
+ },
477
+ }
478
+ )
479
+ ).toBe(
480
+ 'https://api.example.com/edge-cases?bigint=123&symbol=symbol-as-string&func=%5BFunction%5D'
481
+ );
482
+ });
218
483
  });
@@ -115,8 +115,23 @@ export function qraftAPIClient<
115
115
  ): APIBasicQueryClientServices<Services, Callbacks>;
116
116
 
117
117
  /**
118
- * Creates a basic API Client which contains only "fetch" like
119
- * operations without any state management using QueryClient.
118
+ * Creates a basic API Client which contains all hooks and methods
119
+ * that don't require an explicitly provided QueryClient.
120
+ * Hooks like `useQuery` and `useMutation` will automatically retrieve
121
+ * the QueryClient from the `<QueryClientProvider />` context.
122
+ *
123
+ * @example Fetching data with QueryClient from context
124
+ * ```ts
125
+ * const api = qraftAPIClient(services, callbacks, {
126
+ * requestFn: requestFn,
127
+ * baseUrl: 'https://api.example.com',
128
+ * });
129
+ *
130
+ * // QueryClient will be retrieved from React context
131
+ * api.service.operation.useQuery({
132
+ * parameters: { path: { id: 1 } },
133
+ * });
134
+ * ```
120
135
  *
121
136
  * @example Fetching data without QueryClient
122
137
  * ```ts
@@ -140,8 +155,17 @@ export function qraftAPIClient<
140
155
  ): APIBasicClientServices<Services, Callbacks>;
141
156
 
142
157
  /**
143
- * Creates a utility API Client which contains only utility operations
144
- * such as `getQueryKey`, `getInfiniteQueryKey` and `getMutationKey`.
158
+ * Creates a utility API Client which contains utility operations
159
+ * such as `getQueryKey`, `getInfiniteQueryKey`, `getMutationKey` and state hooks
160
+ * like `useIsFetching` and `useMutationData`.
161
+ *
162
+ * @example Using state hooks
163
+ * ```ts
164
+ * const api = qraftAPIClient(services, callbacks);
165
+ *
166
+ * // Check if any query is currently fetching
167
+ * const isFetching = api.service.operation.useIsFetching();
168
+ * ```
145
169
  *
146
170
  * @example Getting query keys with utility client
147
171
  * ```ts
@@ -241,7 +265,17 @@ export function qraftAPIClient<
241
265
  callbackName !== 'operationInvokeFn' &&
242
266
  callbackName !== 'getQueryKey' &&
243
267
  callbackName !== 'getMutationKey' &&
244
- callbackName !== 'getInfiniteQueryKey'
268
+ callbackName !== 'getInfiniteQueryKey' &&
269
+ callbackName !== 'useInfiniteQuery' &&
270
+ callbackName !== 'useQueries' &&
271
+ callbackName !== 'useQuery' &&
272
+ callbackName !== 'useSuspenseInfiniteQuery' &&
273
+ callbackName !== 'useSuspenseQueries' &&
274
+ callbackName !== 'useSuspenseQuery' &&
275
+ callbackName !== 'useIsFetching' &&
276
+ callbackName !== 'useMutation' &&
277
+ callbackName !== 'useIsMutating' &&
278
+ callbackName !== 'useMutationState'
245
279
  )
246
280
  if (!options || !('queryClient' in options && options.queryClient))
247
281
  throw new Error(
@@ -331,7 +365,7 @@ type OperationDeclaration = {
331
365
  Record<
332
366
  | QueryOperationCallbacks
333
367
  | QueryOperationStateCallbacks
334
- | MutationOperationCallbacks
368
+ | MutationOperationHookCallbacks
335
369
  | MutationOperationStateCallbacks
336
370
  | UtilityOperationCallbacks,
337
371
  any
@@ -356,15 +390,8 @@ export type UnionServiceOperationsDeclaration<Services> =
356
390
  | OperationsDeclaration<Services>
357
391
  | OperationDeclaration;
358
392
 
359
- type QueryOperationCallbacks = Extract<
393
+ type QueryOperationHookCallbacks = Extract<
360
394
  keyof ServiceMethods,
361
- | 'fetchInfiniteQuery'
362
- | 'fetchQuery'
363
- | 'prefetchInfiniteQuery'
364
- | 'prefetchQuery'
365
- | 'refetchQueries'
366
- | 'ensureQueryData'
367
- | 'ensureInfiniteQueryData'
368
395
  | 'useInfiniteQuery'
369
396
  | 'useQueries'
370
397
  | 'useQuery'
@@ -373,31 +400,57 @@ type QueryOperationCallbacks = Extract<
373
400
  | 'useSuspenseQuery'
374
401
  >;
375
402
 
376
- type QueryOperationStateCallbacks = Extract<
403
+ type QueryOperationCallbacks =
404
+ | Extract<
405
+ keyof ServiceMethods,
406
+ | 'fetchInfiniteQuery'
407
+ | 'fetchQuery'
408
+ | 'prefetchInfiniteQuery'
409
+ | 'prefetchQuery'
410
+ | 'refetchQueries'
411
+ | 'ensureQueryData'
412
+ | 'ensureInfiniteQueryData'
413
+ >
414
+ | QueryOperationHookCallbacks;
415
+
416
+ type QueryOperationStateHookCallbacks = Extract<
377
417
  keyof ServiceMethods,
378
- | 'cancelQueries'
379
- | 'getInfiniteQueryData'
380
- | 'getInfiniteQueryState'
381
- | 'getQueriesData'
382
- | 'getQueryData'
383
- | 'getQueryState'
384
- | 'invalidateQueries'
385
- | 'isFetching'
386
- | 'removeQueries'
387
- | 'resetQueries'
388
- | 'setInfiniteQueryData'
389
- | 'setQueriesData'
390
- | 'setQueryData'
391
- | 'useIsFetching'
418
+ 'useIsFetching'
392
419
  >;
393
420
 
394
- type MutationOperationCallbacks = Extract<keyof ServiceMethods, 'useMutation'>;
421
+ type QueryOperationStateCallbacks =
422
+ | Extract<
423
+ keyof ServiceMethods,
424
+ | 'cancelQueries'
425
+ | 'getInfiniteQueryData'
426
+ | 'getInfiniteQueryState'
427
+ | 'getQueriesData'
428
+ | 'getQueryData'
429
+ | 'getQueryState'
430
+ | 'invalidateQueries'
431
+ | 'isFetching'
432
+ | 'removeQueries'
433
+ | 'resetQueries'
434
+ | 'setInfiniteQueryData'
435
+ | 'setQueriesData'
436
+ | 'setQueryData'
437
+ >
438
+ | QueryOperationStateHookCallbacks;
395
439
 
396
- type MutationOperationStateCallbacks = Extract<
440
+ type MutationOperationHookCallbacks = Extract<
397
441
  keyof ServiceMethods,
398
- 'isMutating' | 'useIsMutating' | 'useMutationState'
442
+ 'useMutation'
399
443
  >;
400
444
 
445
+ type MutationOperationStateHookCallbacks = Extract<
446
+ keyof ServiceMethods,
447
+ 'useIsMutating' | 'useMutationState'
448
+ >;
449
+
450
+ type MutationOperationStateCallbacks =
451
+ | Extract<keyof ServiceMethods, 'isMutating'>
452
+ | MutationOperationStateHookCallbacks;
453
+
401
454
  type InvokeOperationCallback = Extract<
402
455
  keyof typeof operationInvokeModule,
403
456
  'operationInvokeFn'
@@ -405,13 +458,17 @@ type InvokeOperationCallback = Extract<
405
458
 
406
459
  type UtilityOperationCallbacks = Extract<
407
460
  keyof ServiceMethods,
408
- 'getQueryKey' | 'getInfiniteQueryKey' | 'getMutationKey'
461
+ | 'getQueryKey'
462
+ | 'getInfiniteQueryKey'
463
+ | 'getMutationKey'
464
+ | QueryOperationStateHookCallbacks
465
+ | MutationOperationStateHookCallbacks
409
466
  >;
410
467
 
411
468
  type OperationCallbackList =
412
469
  | QueryOperationCallbacks
413
470
  | QueryOperationStateCallbacks
414
- | MutationOperationCallbacks
471
+ | MutationOperationHookCallbacks
415
472
  | MutationOperationStateCallbacks
416
473
  | InvokeOperationCallback
417
474
  | UtilityOperationCallbacks;
@@ -458,7 +515,15 @@ export type APIBasicClientServices<
458
515
  Callbacks extends PartialServiceMethods,
459
516
  > = ServicesFilteredByCallbacks<
460
517
  Services,
461
- Extract<keyof Callbacks, InvokeOperationCallback | UtilityOperationCallbacks>
518
+ Extract<
519
+ keyof Callbacks,
520
+ | InvokeOperationCallback
521
+ | UtilityOperationCallbacks
522
+ | QueryOperationHookCallbacks
523
+ | QueryOperationStateHookCallbacks
524
+ | MutationOperationStateHookCallbacks
525
+ | MutationOperationHookCallbacks
526
+ >
462
527
  >;
463
528
 
464
529
  export type APIUtilityClientServices<