@empathyco/x-adapter 8.0.0-alpha.2 → 8.0.0-alpha.20

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 (55) hide show
  1. package/README.md +682 -5
  2. package/dist/cjs/http-clients/__mocks__/fetch.mock.js +6 -1
  3. package/dist/cjs/http-clients/__mocks__/fetch.mock.js.map +1 -1
  4. package/dist/cjs/http-clients/fetch.http-client.js +28 -9
  5. package/dist/cjs/http-clients/fetch.http-client.js.map +1 -1
  6. package/dist/cjs/http-clients/index.js +0 -1
  7. package/dist/cjs/http-clients/index.js.map +1 -1
  8. package/dist/cjs/http-clients/types.js.map +1 -1
  9. package/dist/cjs/http-clients/utils.js +2 -3
  10. package/dist/cjs/http-clients/utils.js.map +1 -1
  11. package/dist/cjs/mappers/schema-mapper.factory.js +3 -4
  12. package/dist/cjs/mappers/schema-mapper.factory.js.map +1 -1
  13. package/dist/cjs/schemas/types.js.map +1 -1
  14. package/dist/cjs/schemas/utils.js +7 -5
  15. package/dist/cjs/schemas/utils.js.map +1 -1
  16. package/dist/cjs/utils/index.js +0 -1
  17. package/dist/cjs/utils/index.js.map +1 -1
  18. package/dist/cjs/utils/interpolate.js +2 -2
  19. package/dist/cjs/utils/interpolate.js.map +1 -1
  20. package/dist/esm/http-clients/__mocks__/fetch.mock.js +6 -1
  21. package/dist/esm/http-clients/__mocks__/fetch.mock.js.map +1 -1
  22. package/dist/esm/http-clients/fetch.http-client.js +27 -8
  23. package/dist/esm/http-clients/fetch.http-client.js.map +1 -1
  24. package/dist/esm/http-clients/index.js +0 -1
  25. package/dist/esm/http-clients/index.js.map +1 -1
  26. package/dist/esm/http-clients/types.js.map +1 -1
  27. package/dist/esm/http-clients/utils.js +3 -4
  28. package/dist/esm/http-clients/utils.js.map +1 -1
  29. package/dist/esm/mappers/schema-mapper.factory.js +4 -5
  30. package/dist/esm/mappers/schema-mapper.factory.js.map +1 -1
  31. package/dist/esm/schemas/types.js.map +1 -1
  32. package/dist/esm/schemas/utils.js +7 -5
  33. package/dist/esm/schemas/utils.js.map +1 -1
  34. package/dist/esm/utils/index.js +0 -1
  35. package/dist/esm/utils/index.js.map +1 -1
  36. package/dist/esm/utils/interpolate.js +2 -2
  37. package/dist/esm/utils/interpolate.js.map +1 -1
  38. package/dist/types/http-clients/__mocks__/fetch.mock.d.ts +2 -2
  39. package/dist/types/http-clients/index.d.ts +0 -1
  40. package/dist/types/http-clients/types.d.ts +8 -0
  41. package/dist/types/mappers/schema-mapper.factory.d.ts +2 -2
  42. package/dist/types/schemas/types.d.ts +8 -6
  43. package/dist/types/schemas/utils.d.ts +1 -1
  44. package/dist/types/utils/index.d.ts +0 -1
  45. package/package.json +9 -7
  46. package/dist/cjs/http-clients/beacon.http-client.js +0 -60
  47. package/dist/cjs/http-clients/beacon.http-client.js.map +0 -1
  48. package/dist/cjs/utils/extract-value.js +0 -28
  49. package/dist/cjs/utils/extract-value.js.map +0 -1
  50. package/dist/esm/http-clients/beacon.http-client.js +0 -56
  51. package/dist/esm/http-clients/beacon.http-client.js.map +0 -1
  52. package/dist/esm/utils/extract-value.js +0 -24
  53. package/dist/esm/utils/extract-value.js.map +0 -1
  54. package/dist/types/http-clients/beacon.http-client.d.ts +0 -14
  55. package/dist/types/utils/extract-value.d.ts +0 -12
package/README.md CHANGED
@@ -1,13 +1,690 @@
1
1
  # x-adapter
2
2
 
3
- `x-adapter` is a library of utils to ease the communication with any API. Some features that it
4
- provides:
3
+ **Empathy Adapter** is a library of utils to ease the communication with any API.
4
+
5
+ Some features that it provides:
5
6
 
6
7
  - Create an API request function based on a simple configuration.
8
+ - Allow to configure several endpoints by extending the initial configuration.
7
9
  - Allow to configure the response/request mapping.
8
10
  - Create mapping functions based on Schemas.
9
11
 
10
- ### Contributing
12
+ <br>
13
+
14
+ ## Tech Stack
15
+
16
+ [![TypeScript](https://img.shields.io/badge/-typescript-3178C6?logo=typescript&logoColor=white&style=for-the-badge)](https://www.typescriptlang.org/)
17
+ [![Jest](https://img.shields.io/badge/-jest-C21325?logo=jest&logoColor=white&style=for-the-badge)](https://jestjs.io/)
18
+
19
+ <br>
20
+
21
+ ## Installation
22
+
23
+ ```
24
+ npm i @empathyco/x-adapter
25
+ ```
26
+
27
+ If you use this package together with
28
+ [x-components](https://github.com/empathyco/x/tree/main/packages/x-components), you should
29
+ additionally install the
30
+ [@empathyco/x-types](https://github.com/empathyco/x/tree/main/packages/search-types) package and
31
+ take the advantage of it in your project development.
32
+
33
+ <br>
34
+
35
+ ## Configuration & Usage
36
+
37
+ An `API Adapter` is a collection of `EndpointAdapters`, one for each endpoint of the API you want to
38
+ consume. Each `EndpointAdapter` is an asynchronous function that performs a request with the given
39
+ data, and returns a response promise with the requested data. Internally, it usually has to
40
+ transform the request data so the API can understand it, and the response data so your app
41
+ understands it as well.
42
+
43
+ <br>
44
+
45
+ ### Implement your own adapter
46
+
47
+ To create an `EndpointAdapter` you can use the `endpointAdapterFactory` function. This function will
48
+ receive an `EndpointAdapterOptions` object containing all the needed data to perform and map a
49
+ request, and return a function that when invoked will trigger the request. The options that can be
50
+ configured are:
51
+
52
+ - `endpoint`: The URL that the `httpClient` uses. It can be either a string or a mapper function
53
+ that dynamically generates the URL string using the request data.
54
+ - `httpClient`: A function that will receive the endpoint and request options such as the parameters
55
+ and will perform the request, returning a promise with the unprocessed response data.
56
+ - `defaultRequestOptions`: Default values for the endpoint configuration. You can use it to define
57
+ if a request is cancelable, a unique id to identify it, anything but the `endpoint` can be set.
58
+ Check
59
+ [EndpointAdapterOptions](https://github.com/empathyco/x/blob/main/packages/x-adapter/src/endpoint-adapter/types.ts)
60
+ to see the available options.
61
+ - `requestMapper`: A function to transform the unprocessed request into parameters the API can
62
+ understand.
63
+ - `responseMapper`: A function to transform the API response into data that your project can
64
+ understand.
65
+
66
+ <br>
67
+
68
+ #### Basic adapter implementation
69
+
70
+ In this example we have a simple request mapper that will add a `q` parameter to the endpoint's url
71
+ to perform the request. If you check the function call below, you will see the query parameter
72
+ passed.
73
+
74
+ ###### Types definition
75
+
76
+ ```ts
77
+ // API models
78
+ interface ApiRequest {
79
+ q?: string;
80
+ id?: number;
81
+ }
82
+ interface ApiSearchResponse {
83
+ products: ApiProduct[];
84
+ total: number;
85
+ }
86
+ interface ApiProduct {
87
+ id: number;
88
+ title: string;
89
+ price: number;
90
+ }
91
+
92
+ // App models
93
+ interface AppSearchRequest {
94
+ query: string;
95
+ }
96
+ interface AppSearchResponse {
97
+ products: AppProduct[];
98
+ total: number;
99
+ }
100
+ interface AppProduct {
101
+ id: string;
102
+ name: string;
103
+ price: number;
104
+ }
105
+ ```
106
+
107
+ ###### Adapter's factory function implementation
108
+
109
+ ```ts
110
+ import { endpointAdapterFactory } from '@empathyco/x-adapter';
111
+
112
+ export const searchProducts = endpointAdapterFactory({
113
+ endpoint: 'https://dummyjson.com/products/search',
114
+ requestMapper({ query }: Readonly<AppSearchRequest>): ApiRequest {
115
+ return {
116
+ q: query // the request will be triggered as https://dummyjson.com/products/search?q=phone
117
+ };
118
+ },
119
+ responseMapper({ products, total }: Readonly<ApiSearchResponse>): AppSearchResponse {
120
+ return {
121
+ products: products.map(product => {
122
+ return {
123
+ id: product.id.toString(),
124
+ name: product.title,
125
+ price: product.price
126
+ };
127
+ }),
128
+ total: total
129
+ };
130
+ }
131
+ });
132
+
133
+ // Function call
134
+ async function searchOnClick() {
135
+ const response = await searchProducts({ query: 'phone' });
136
+ console.log('products', response.products);
137
+ }
138
+ ```
139
+
140
+ <br>
141
+
142
+ #### Using a dynamic endpoint
143
+
144
+ If you need to generate an endpoint url dynamically, you can add parameters inside curly brackets to
145
+ the endpoint string. By default, these parameters will be replaced using the request data. If a
146
+ parameter is not found inside the request, an empty string will be used.
147
+
148
+ ```ts
149
+ export const getItemById = endpointAdapterFactory({
150
+ endpoint: 'https://dummyjson.com/{section}/{id}'
151
+ // ... rest of options to configure
152
+ });
153
+ getItemById({ section: 'products', id: 1 }); // 'https://dummyjson.com/products/1'
154
+ getItemById({ section: 'quotes', id: 3 }); // 'https://dummyjson.com/quotes/3'
155
+ getItemById({ section: 'quotes' }); // 'https://dummyjson.com/quotes/'
156
+ ```
157
+
158
+ For more complex use cases, you can use a mapper function. This function receives the request, and
159
+ must return the URL string.
160
+
161
+ ```ts
162
+ export const getProductById = endpointAdapterFactory({
163
+ endpoint: ({ id }: GetProductByIdRequest) => 'https://dummyjson.com/products/' + id
164
+ // ... rest of options to configure
165
+ });
166
+ ```
167
+
168
+ Additionally, you can also overwrite your adapter's endpoint definition using the
169
+ `RequestOptions.endpoint` parameter in the function call. Take into account that your
170
+ `responseMapper` definition should be agnostic enough to support the change:
171
+
172
+ ```ts
173
+ export const getItemById = endpointAdapterFactory({
174
+ endpoint: 'https://dummyjson.com/quotes/{id}',
175
+ // ... rest of options to configure
176
+ });
177
+
178
+ // You would pass the new endpoint in the function call
179
+ getItemById({ id: 1 }, { endpoint: 'https://dummyjson.com/products/{id}');
180
+ ```
181
+
182
+ <br>
183
+
184
+ #### Using the httpClient function
185
+
186
+ Every adapter created using `endpointAdapterFactory` uses the `Fetch API` by default to perform the
187
+ requests. But you can use your own `HttpClient` as part of the configurable
188
+ `EndpointAdapterOptions`. A `HttpClient` is a function that accepts two parameters: the `endpoint`
189
+ string, and an additional
190
+ [`options`](https://github.com/empathyco/x/blob/main/packages/x-adapter/src/http-clients/types.ts)
191
+ object to make the request with.
192
+
193
+ ```ts
194
+ // HTTP Client
195
+ const axiosHttpClient: HttpClient = (endpoint, options) =>
196
+ axios.get(endpoint, { params: options?.parameters }).then(response => response.data);
197
+
198
+ // Request Mapper
199
+ const customRequestMapper: Mapper<AppSearchRequest, ApiRequest> = ({ query }) => {
200
+ return {
201
+ q: query
202
+ };
203
+ };
204
+
205
+ // Response Mapper
206
+ const customResponseMapper: Mapper<ApiSearchResponse, AppSearchResponse> = ({
207
+ products,
208
+ total
209
+ }) => {
210
+ return {
211
+ products: products.map(product => {
212
+ return {
213
+ id: product.id.toString(),
214
+ name: product.title,
215
+ price: product.price
216
+ };
217
+ }),
218
+ total: total
219
+ };
220
+ };
221
+
222
+ // Adapter factory function implementation
223
+ export const searchProducts = endpointAdapterFactory({
224
+ endpoint: 'https://dummyjson.com/products/search',
225
+ httpClient: axiosHttpClient,
226
+ requestMapper: customRequestMapper,
227
+ responseMapper: customResponseMapper
228
+ });
229
+ ```
230
+
231
+ <br>
232
+
233
+ ### Implement your own adapter using schemas
234
+
235
+ Sometimes the transformations you will need to do in the mappers are just renaming parameters. What
236
+ the API calls `q` might be called `query` in your request. To ease this transformations,
237
+ `@empathyco/x-adapter` allows to create mappers using schemas.
238
+
239
+ A schema is just a dictionary where the key is the desired parameter name, and the value is the path
240
+ of the source object that has the desired value or a simple mapper function if you need to transform
241
+ the value somehow.
242
+
243
+ ###### Types definition
244
+
245
+ ```ts
246
+ // API models
247
+ interface ApiUserRequest {
248
+ q: string;
249
+ }
250
+ interface ApiUserResponse {
251
+ users: ApiUser[];
252
+ total: number;
253
+ }
254
+ interface ApiUser {
255
+ id: number;
256
+ firstName: string;
257
+ }
258
+
259
+ // App models
260
+ interface AppUserRequest {
261
+ query: string;
262
+ }
263
+ interface AppUserResponse {
264
+ people: AppUser[];
265
+ total: number;
266
+ }
267
+ interface AppUser {
268
+ id: string;
269
+ name: string;
270
+ }
271
+ ```
272
+
273
+ ###### Schema's mapper factory function implementation
274
+
275
+ ```ts
276
+ // Map both the request and the response to connect your model with the API you are working with.
277
+ const userSearchRequestMapper = schemaMapperFactory<AppUserRequest, ApiUserRequest>({
278
+ q: 'query'
279
+ });
280
+ const userSearchResponseMapper = schemaMapperFactory<ApiUserResponse, AppUserResponse>({
281
+ people: ({ users }) =>
282
+ users.map(user => {
283
+ return {
284
+ id: user.id.toString(),
285
+ name: user.firstName
286
+ };
287
+ }),
288
+ total: 'total'
289
+ });
290
+
291
+ // Use the mappers in the Endpoint's adapter factory function
292
+ export const searchUsers = endpointAdapterFactory({
293
+ endpoint: 'https://dummyjson.com/users/search',
294
+ requestMapper: userSearchRequestMapper,
295
+ responseMapper: userSearchResponseMapper
296
+ });
297
+ ```
298
+
299
+ <br>
300
+
301
+ #### Create more complex models with SubSchemas
302
+
303
+ When you are creating adapters for different APIs you might find the case that you have to map the
304
+ same model in different places. To help you with that, schemas allows to use `SubSchemas`. To use
305
+ them you just have to provide with the `Path` of the data to map, and the `Schema` to apply to it.
306
+
307
+ ###### Types definition
308
+
309
+ ```ts
310
+ // API models
311
+ interface ApiRequest {
312
+ q: string;
313
+ }
314
+ interface ApiResponse {
315
+ users: ApiUser[];
316
+ total: number;
317
+ }
318
+ interface ApiUser {
319
+ id: number;
320
+ email: string;
321
+ phone: string;
322
+ address: ApiAddress;
323
+ company: ApiAddress;
324
+ }
325
+ interface ApiAddress {
326
+ address: string;
327
+ city: string;
328
+ postalCode: string;
329
+ }
330
+
331
+ // App models
332
+ interface AppRequest {
333
+ query: string;
334
+ }
335
+ interface AppResponse {
336
+ people: AppUser[];
337
+ count: number;
338
+ }
339
+ interface AppUser {
340
+ id: number;
341
+ contact: {
342
+ email: string;
343
+ phone: string;
344
+ homeAddress: AppAddress;
345
+ companyAddress: AppAddress;
346
+ };
347
+ }
348
+ interface AppAddress {
349
+ displayName: string;
350
+ postalCode: number;
351
+ city: string;
352
+ }
353
+ ```
354
+
355
+ ###### Schemas and SubSchemas implementation
356
+
357
+ ```ts
358
+ // Address Schema definition
359
+ const addressSchema: Schema<ApiAddress, AppUserAddress> = {
360
+ displayName: source => `${source.address}, ${source.postalCode} - ${source.city}`,
361
+ city: 'city',
362
+ postalCode: source => parseInt(source.postalCode)
363
+ };
364
+
365
+ // User Schema definition with a subSchema
366
+ const userSchema: Schema<ApiUser, AppUser> = {
367
+ id: 'id',
368
+ contact: {
369
+ email: source => source.email.toLowerCase(),
370
+ phone: 'phone',
371
+ homeAddress: {
372
+ $subSchema: addressSchema,
373
+ $path: 'address'
374
+ },
375
+ companyAddress: {
376
+ $subSchema: addressSchema,
377
+ $path: 'address'
378
+ }
379
+ }
380
+ };
381
+ ```
382
+
383
+ ###### Schema's mapper factory function implementation with subSchemas
384
+
385
+ ```ts
386
+ // Response mapper with user's subSchema implemented
387
+ const responseMapper = schemaMapperFactory<ApiSearchUsersResponse, AppSearchUsersResponse>({
388
+ people: {
389
+ $subSchema: userSchema,
390
+ $path: 'users'
391
+ },
392
+ count: 'total'
393
+ });
394
+
395
+ const requestMapper = schemaMapperFactory<SearchUsersRequest, ApiSearchUsersRequest>({
396
+ q: 'query'
397
+ });
398
+
399
+ // Endpoint Adapter Factory function implementation
400
+ export const searchUsersWithContactInfo = endpointAdapterFactory({
401
+ endpoint: 'https://dummyjson.com/users/search',
402
+ requestMapper,
403
+ responseMapper
404
+ });
405
+ ```
406
+
407
+ <br>
408
+
409
+ #### Using a mutable schema
410
+
411
+ This feature lets you have some default schemas, and modify or extend them for some concrete
412
+ implementations. To do so, you can use the `createMutableSchema` function, passing a `Source` and
413
+ `Target` type parameters to map your models. This function will return a `MutableSchema` that apart
414
+ from the mapping information will also contain some methods to create new schemas or modify the
415
+ current one.
416
+
417
+ In the example below we will create a `MutableSchema` to have a default object that will be reused
418
+ for different endpoint calls.
419
+
420
+ ###### Types definition and MutableSchema
421
+
422
+ ```ts
423
+ // API models
424
+ export interface ApiBaseObject {
425
+ id: number;
426
+ body: string;
427
+ }
428
+
429
+ // APP models
430
+ export interface AppBaseObject {
431
+ id: string;
432
+ text: string;
433
+ }
434
+
435
+ // Mutable Schema
436
+ export const baseObjectSchema = createMutableSchema<ApiBaseObject, AppBaseObject>({
437
+ id: ({ id }) => id.toString(),
438
+ text: 'body'
439
+ });
440
+ ```
441
+
442
+ Once we have the `MutableSchema`, we can use the following methods to fit our different APIs needs:
443
+
444
+ - `$extends`: Creates a new `MutableSchema` based on the original one. The original remains
445
+ unchanged. This can be useful if we need to create a new `EndpointAdapter` with models based on
446
+ another API.
447
+ - `$override`: Merges/modifies the original `MutableSchema` partially, so the change will affect to
448
+ all the `EndpointAdapter`(s) that are using it. It can be used to change the structure of our
449
+ request/response mappers, or to add them new fields. Useful for clients with few differences in
450
+ their APIs. For example, you can create a library with a default adapter and use this library from
451
+ the customer projects overriding only the needed field (e.g. retrieve the images from `pictures`
452
+ instead of `images` in a products API).
453
+ - `$replace`: Replaces completely the original `MutableSchema` by a new one, it won't exist anymore.
454
+ The change will affect to all the `EndpointAdapter`(s) that were using it. Useful for clients with
455
+ a completely different API/response to the standard you have been working with.
456
+
457
+ ###### Extend a MutableSchema to reuse it in two different endpoints with more fields
458
+
459
+ ```ts
460
+ import { ApiBaseObject, AppBaseObject, baseObjectSchema } from '@/base-types';
461
+
462
+ // Api models
463
+ interface ApiPost extends ApiBaseObject {
464
+ title: string;
465
+ }
466
+ interface ApiPostsResponse {
467
+ posts: ApiPost[];
468
+ }
469
+
470
+ interface ApiComment extends ApiBaseObject {
471
+ postId: number;
472
+ }
473
+ interface ApiCommentsResponse {
474
+ comments: ApiComment[];
475
+ }
476
+
477
+ // App models
478
+ interface AppPost extends AppBaseObject {
479
+ postTitle: string;
480
+ }
481
+ interface AppPostsResponse {
482
+ posts: AppPost[];
483
+ }
484
+
485
+ interface AppComment extends AppBaseObject {
486
+ postId: number;
487
+ }
488
+ interface AppCommentsResponse {
489
+ comments: AppComment[];
490
+ }
491
+
492
+ // Extend for posts endpoint
493
+ const postSchema = baseObjectSchema.$extends<ApiPost, AppPost>({
494
+ postTitle: 'title'
495
+ });
496
+
497
+ const postsResponse = schemaMapperFactory<ApiPostsResponse, AppPostsResponse>({
498
+ posts: {
499
+ $subSchema: postSchema,
500
+ $path: 'posts'
501
+ }
502
+ });
503
+
504
+ export const searchPosts = endpointAdapterFactory({
505
+ endpoint: 'https://dummyjson.com/posts',
506
+ responseMapper: postsResponse
507
+ });
508
+
509
+ // Extend for comments endpoint
510
+ const commentSchema = baseObjectSchema.$extends<ApiComment, AppComment>({
511
+ postId: 'postId'
512
+ });
513
+
514
+ const commentsResponse = schemaMapperFactory<ApiCommentsResponse, AppCommentsResponse>({
515
+ comments: {
516
+ $subSchema: commentSchema,
517
+ $path: 'comments'
518
+ }
519
+ });
520
+
521
+ export const searchComments = endpointAdapterFactory({
522
+ endpoint: 'https://dummyjson.com/comments',
523
+ responseMapper: commentsResponse
524
+ });
525
+ ```
526
+
527
+ ###### Override a MutableSchema to add more fields
528
+
529
+ As said above, the suitable context for using the `override` method would be a project with an API
530
+ that doesn't differ too much against the one used in our "base project". That means we can reuse
531
+ most of the types and schemas definitions, so we would only add a few new fields from the new API.
532
+
533
+ ```ts
534
+ import { ApiBaseObject, AppBaseObject, baseObjectSchema } from '@/base-types';
535
+
536
+ // Api models
537
+ interface ApiTodo {
538
+ completed: boolean;
539
+ todo: string;
540
+ userId: number;
541
+ }
542
+
543
+ interface ApiTodosResponse {
544
+ todos: ApiBaseObject[];
545
+ }
546
+
547
+ // App models
548
+ interface AppTodo {
549
+ completed: boolean;
550
+ text: string;
551
+ userId: string;
552
+ }
553
+
554
+ interface AppTodosResponse {
555
+ todos: AppBaseObject[];
556
+ }
557
+
558
+ // Response mapper
559
+ const todosResponse = schemaMapperFactory<ApiTodosResponse, AppTodosResponse>({
560
+ todos: {
561
+ $subSchema: baseObjectSchema,
562
+ $path: 'todos'
563
+ }
564
+ });
565
+
566
+ // Endpoint Adapter
567
+ export const searchTodos = endpointAdapterFactory({
568
+ endpoint: 'https://dummyjson.com/todos',
569
+ responseMapper: todosResponse
570
+ });
571
+
572
+ // Override the original Schema. The Schema changes to map: 'id', 'completed', 'text' and 'userId''
573
+ baseObjectSchema.$override<ApiTodo, AppTodo>({
574
+ completed: 'completed',
575
+ text: 'todo',
576
+ userId: ({ userId }) => userId.toString()
577
+ });
578
+ ```
579
+
580
+ ###### Replace a MutableSchema to completely change it
581
+
582
+ In this case we are facing too many differences between API responses. We don't need to write a
583
+ whole adapter from scratch, as there are other parts of the API that aren't changing so much, but we
584
+ should replace some `endpointAdapter`'s schemas.
585
+
586
+ ```ts
587
+ import { ApiBaseObject, AppBaseObject, baseObjectSchema } from '@/base-types';
588
+
589
+ // Api models
590
+ interface ApiQuote {
591
+ id: number;
592
+ quote: string;
593
+ author: string;
594
+ }
595
+
596
+ interface ApiQuotesResponse {
597
+ quotes: ApiBaseObject[];
598
+ }
599
+
600
+ // App models
601
+ interface AppQuote {
602
+ quoteId: string;
603
+ quote: string;
604
+ author: string;
605
+ }
606
+
607
+ interface AppQuotesResponse {
608
+ quotes: AppBaseObject[];
609
+ }
610
+
611
+ // Response mapper
612
+ const quotesResponse = schemaMapperFactory<ApiQuotesResponse, AppQuotesResponse>({
613
+ quotes: {
614
+ $subSchema: baseObjectSchema,
615
+ $path: 'quotes'
616
+ }
617
+ });
618
+
619
+ // Endpoint Adapter
620
+ export const searchQuotes = endpointAdapterFactory({
621
+ endpoint: 'https://dummyjson.com/quotes',
622
+ responseMapper: quotesResponse
623
+ });
624
+
625
+ // Replace the original Schema
626
+ baseObjectSchema.$replace<ApiQuote, AppQuote>({
627
+ quoteId: ({ id }) => id.toString(),
628
+ quote: 'quote',
629
+ author: 'author'
630
+ });
631
+ ```
632
+
633
+ <br>
634
+
635
+ ### Extend an adapter that uses schemas
636
+
637
+ Imagine you have a new setup and that you can reuse most of the stuff you have developed. Probably
638
+ you have built an adapter instance as a configuration object that contains all of your
639
+ `EndpointAdapter` calls, so you only need to extend the endpoint you need to change.
640
+
641
+ ```ts
642
+ export const adapter = {
643
+ searchItem: getItemById,
644
+ searchList: searchComments
645
+ // Any endpoint adapter you are using to communicate with your API
646
+ };
647
+
648
+ adapter.searchList = searchComments.extends({
649
+ endpoint: 'https://dummyjson.com/comments/',
650
+ defaultRequestOptions: {
651
+ // If you need to send an id, a header...
652
+ },
653
+ defaultRequestOptions: {
654
+ parameters: {
655
+ limit: 10,
656
+ skip: 10
657
+ }
658
+ }
659
+ });
660
+ ```
661
+
662
+ For further detail, you can check the
663
+ [x-platform-adapter](https://github.com/empathyco/x/tree/main/packages/x-adapter-platform) package.
664
+ It is a whole adapter implementation using this `x-adapter` library to suit the
665
+ [Search Platform API](https://docs.empathy.co/develop-empathy-platform/api-reference/search-api.html)
666
+ needs.
667
+
668
+ ## Test
669
+
670
+ **Empathy Adapter** features are tested using [Jest](https://jestjs.io/). You will find a
671
+ `__tests__` folder inside each of the project's sections.
672
+
673
+ ```
674
+ npm test
675
+ ```
676
+
677
+ ## Changelog
678
+
679
+ [Changelog summary](https://github.com/empathyco/x/blob/main/packages/x-adapter/CHANGELOG.md)
680
+
681
+ ## Contributing
682
+
683
+ To start contributing to the project, please take a look at our
684
+ **[Contributing Guide](https://github.com/empathyco/x/blob/main/.github/CONTRIBUTING.md).** Take in
685
+ account that `x-adapter` is developed using [Typescript](https://www.typescriptlang.org/), so we
686
+ recommend you to check it out.
687
+
688
+ ## License
11
689
 
12
- To start contributing to the project, please take a look to our
13
- [Contributing Guide](../../.github/CONTRIBUTING.md).
690
+ [empathyco/x License](https://github.com/empathyco/x/blob/main/packages/x-adapter/LICENSE)
@@ -24,7 +24,12 @@ function fetchMock(response) {
24
24
  reject(new DOMException('Aborted', 'AbortError'));
25
25
  }
26
26
  else {
27
- resolve({ ok: true, json: () => Promise.resolve(response) });
27
+ resolve({
28
+ ok: true,
29
+ status: 200,
30
+ json: () => Promise.resolve(response),
31
+ text: () => Promise.resolve(JSON.stringify(response))
32
+ });
28
33
  }
29
34
  });
30
35
  });