@or-sdk/contacts 3.5.5 → 3.5.6

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 (42) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/Contacts.js +14 -0
  3. package/dist/cjs/Contacts.js.map +1 -1
  4. package/dist/cjs/api/baseWithPolingApi.js +106 -0
  5. package/dist/cjs/api/baseWithPolingApi.js.map +1 -0
  6. package/dist/cjs/api/batchProcessApi.js +30 -0
  7. package/dist/cjs/api/batchProcessApi.js.map +1 -1
  8. package/dist/cjs/api/bulkContactsCreateApi.js +233 -0
  9. package/dist/cjs/api/bulkContactsCreateApi.js.map +1 -0
  10. package/dist/cjs/api/contactApi.js +6 -40
  11. package/dist/cjs/api/contactApi.js.map +1 -1
  12. package/dist/cjs/types.js.map +1 -1
  13. package/dist/esm/Contacts.js +14 -0
  14. package/dist/esm/Contacts.js.map +1 -1
  15. package/dist/esm/api/baseWithPolingApi.js +47 -0
  16. package/dist/esm/api/baseWithPolingApi.js.map +1 -0
  17. package/dist/esm/api/batchProcessApi.js +25 -1
  18. package/dist/esm/api/batchProcessApi.js.map +1 -1
  19. package/dist/esm/api/bulkContactsCreateApi.js +118 -0
  20. package/dist/esm/api/bulkContactsCreateApi.js.map +1 -0
  21. package/dist/esm/api/contactApi.js +4 -30
  22. package/dist/esm/api/contactApi.js.map +1 -1
  23. package/dist/esm/types.js.map +1 -1
  24. package/dist/types/Contacts.d.ts +4 -0
  25. package/dist/types/Contacts.d.ts.map +1 -1
  26. package/dist/types/api/baseWithPolingApi.d.ts +11 -0
  27. package/dist/types/api/baseWithPolingApi.d.ts.map +1 -0
  28. package/dist/types/api/batchProcessApi.d.ts +3 -0
  29. package/dist/types/api/batchProcessApi.d.ts.map +1 -1
  30. package/dist/types/api/bulkContactsCreateApi.d.ts +17 -0
  31. package/dist/types/api/bulkContactsCreateApi.d.ts.map +1 -0
  32. package/dist/types/api/contactApi.d.ts +3 -4
  33. package/dist/types/api/contactApi.d.ts.map +1 -1
  34. package/dist/types/types.d.ts +10 -1
  35. package/dist/types/types.d.ts.map +1 -1
  36. package/package.json +3 -3
  37. package/src/Contacts.ts +17 -0
  38. package/src/api/baseWithPolingApi.ts +51 -0
  39. package/src/api/batchProcessApi.ts +25 -1
  40. package/src/api/bulkContactsCreateApi.ts +167 -0
  41. package/src/api/contactApi.ts +5 -40
  42. package/src/types.ts +16 -0
@@ -1,7 +1,10 @@
1
1
  import { TrackBatchProcessResponse } from '../types';
2
2
  import { BaseApi } from './baseApi';
3
3
 
4
- import { BatchProcessResponseDto, BatchProcessStatus } from '@onereach/types-contacts-api';
4
+ import {
5
+ BatchProcessResponseDto,
6
+ BatchProcessStatus,
7
+ } from '@onereach/types-contacts-api';
5
8
 
6
9
  export default class BatchProcessApi extends BaseApi {
7
10
  private readonly apiBasePath = 'batch-process';
@@ -43,4 +46,25 @@ export default class BatchProcessApi extends BaseApi {
43
46
  return result;
44
47
  }
45
48
 
49
+ async getBatchProcessesGroup(groupId: string): Promise<BatchProcessResponseDto[]> {
50
+ return this.apiCall({
51
+ method: 'GET',
52
+ route: `${this.apiBasePath}/group/${groupId}`,
53
+ });
54
+ }
55
+
56
+ async indicesOn(): Promise<void> {
57
+ return this.apiCall({
58
+ method: 'POST',
59
+ route: `${this.apiBasePath}/indices-on`,
60
+ });
61
+ }
62
+
63
+ async indicesOff(): Promise<void> {
64
+ return this.apiCall({
65
+ method: 'POST',
66
+ route: `${this.apiBasePath}/indices-off`,
67
+ });
68
+ }
69
+
46
70
  }
@@ -0,0 +1,167 @@
1
+ import { CalApiParams } from '@or-sdk/base';
2
+ import BaseWithPoling from './baseWithPolingApi';
3
+ import BatchProcessApi from './batchProcessApi';
4
+ import { REQUEST_PAYLOAD_MAX_BYTES } from '../constants';
5
+ import { chunkArrByMaxSize, debouncePromise, getObjectSizeInBytes } from '../utils';
6
+ import { v4 } from 'uuid';
7
+ import { BulkContactDto } from '@onereach/types-contacts-api';
8
+ import { BatchBulkCreateData, BulkCreateData, CreateBulkResults } from '../types';
9
+
10
+ export default class BulkContactsCreateApi extends BaseWithPoling {
11
+ constructor(
12
+ protected readonly apiCall: <T>(params: CalApiParams) => Promise<T>,
13
+ protected batchProcessApi: BatchProcessApi,
14
+ ) {
15
+ super(apiCall, batchProcessApi);
16
+ }
17
+
18
+ private readonly apiBasePath = 'bulk-create';
19
+
20
+ /**
21
+ * Inserts multiple contacts into DB.
22
+ * Returns the following structure:
23
+ *
24
+ * ```
25
+ * {
26
+ * created: {
27
+ * contactKey(1): insertedContactId(1),
28
+ * ...
29
+ * contactKey(n): insertedContactId(n),
30
+ * },
31
+ * failed: {
32
+ * contactKey(m): failure description(m)
33
+ * ...
34
+ * contactKey(m): failure description(l)
35
+ * }
36
+ * }
37
+ * ```
38
+ *
39
+ * So this method always succeeds even if it fails to insert all passed contacts - in this case
40
+ * the `created` property is going to be an empty object.
41
+ * contactKey here is going to be a string uniquely (in the scope of passed contacts) identifying
42
+ * the appropriated contact.
43
+ * In the case if contactKey is not defined, it it will be automatically set to the index (as string)
44
+ * of contact.
45
+ */
46
+ async bulkCreateContacts(
47
+ data: BulkCreateData
48
+ ): Promise<CreateBulkResults> {
49
+ const dataWithContactKeys = this.updateContactsWithContactKeys(data);
50
+
51
+ await this.batchProcessApi.indicesOff();
52
+ let results: CreateBulkResults = {
53
+ created: {},
54
+ failed: {},
55
+ };
56
+ try {
57
+ const { contacts, ...rest } = dataWithContactKeys;
58
+ const contactsMaxSize = REQUEST_PAYLOAD_MAX_BYTES - getObjectSizeInBytes({ ...rest });
59
+ const contactsChunks = chunkArrByMaxSize(contacts, contactsMaxSize);
60
+ const batchGroupId = v4();
61
+
62
+ if (contactsChunks.length === 1) {
63
+ await this.createContactsInSingleBatch(dataWithContactKeys as BatchBulkCreateData, batchGroupId);
64
+ } else {
65
+ await this.createContactsInMultiBatches(contactsChunks as BulkContactDto[][], rest, batchGroupId);
66
+ }
67
+
68
+ results = await this.getBulkCreateResults(batchGroupId);
69
+ } finally {
70
+ await this.batchProcessApi.indicesOn();
71
+ }
72
+
73
+ return results;
74
+ }
75
+
76
+ private updateContactsWithContactKeys(data: BulkCreateData): BatchBulkCreateData {
77
+ const dataWithContactKeys = {
78
+ ...data,
79
+ contacts: data.contacts.map((c, idx) => {
80
+ if (c.contactKey !== undefined) {
81
+ return c;
82
+ }
83
+ return {
84
+ ...c,
85
+ contactKey: `${idx}`,
86
+ };
87
+ }) as BulkContactDto[],
88
+ };
89
+
90
+ this.checkContactKeysDuplications(dataWithContactKeys.contacts);
91
+
92
+ return dataWithContactKeys;
93
+ }
94
+
95
+ private checkContactKeysDuplications(contacts: BulkContactDto[]): void {
96
+ contacts.map(({ contactKey }) => contactKey).forEach((key, index, array) => {
97
+ if (index !== array.lastIndexOf(key)) {
98
+ throw new Error(`The ${key} contactKey is assigned to more than 1 contact...`);
99
+ }
100
+ });
101
+ }
102
+
103
+ private async getBulkCreateResults(batchGroupId: string): Promise<CreateBulkResults> {
104
+ const batchProcessesGroup = await this.batchProcessApi.getBatchProcessesGroup(batchGroupId);
105
+ return batchProcessesGroup.reduce<CreateBulkResults>((acc, batch) => {
106
+ const createdContacts = batch.results.reduce<Record<string, string>>((acc, result) => ({
107
+ ...acc,
108
+ ...JSON.parse(result),
109
+ }), {});
110
+ const failedContacts = batch.messages.reduce<Record<string, string>>((acc, message) => ({
111
+ ...acc,
112
+ ...JSON.parse(message),
113
+ }), {});
114
+ return {
115
+ created: {
116
+ ...acc.created,
117
+ ...createdContacts,
118
+ },
119
+ failed: {
120
+ ...acc.failed,
121
+ ...failedContacts,
122
+ },
123
+ };
124
+ }, {
125
+ created: {},
126
+ failed: {},
127
+ });
128
+ }
129
+
130
+ private async createContactsInSingleBatch(
131
+ data: BatchBulkCreateData,
132
+ batchGroupId: string
133
+ ): Promise<void> {
134
+ const batchId = v4();
135
+ this.apiCall({
136
+ method: 'POST',
137
+ route: `${this.apiBasePath}/bulk`,
138
+ data: {
139
+ ...data,
140
+ batchId,
141
+ batchGroupId,
142
+ },
143
+ });
144
+ // giving time to 2 lambdas to wake up (1 - create Batch and 2 - ping batch status)
145
+ await debouncePromise(() => this.polling(batchId), 3000);
146
+ }
147
+
148
+ private async createContactsInMultiBatches(
149
+ contactsChunks: BulkContactDto[][],
150
+ data: Omit<BulkCreateData, 'contacts'>,
151
+ batchGroupId: string,
152
+ ): Promise<void> {
153
+ const batchPromises = contactsChunks
154
+ .map((chunkContacts) => async () => this.createContactsInSingleBatch({
155
+ contacts: chunkContacts,
156
+ ...data,
157
+ }, batchGroupId));
158
+
159
+ const promisesBatchSize = 4;
160
+
161
+ for (let i = 0;i < batchPromises.length;i += promisesBatchSize) {
162
+ const batch = batchPromises.slice(i, i + promisesBatchSize);
163
+ await Promise.all(batch.map((p) => p()));
164
+ }
165
+ }
166
+
167
+ }
@@ -2,7 +2,6 @@ import { v4 } from 'uuid';
2
2
 
3
3
  import {
4
4
  BatchProcessResponseDto,
5
- BatchProcessStatus,
6
5
  ContactParamsDto,
7
6
  ContactRequestDto,
8
7
  ContactResponseDto,
@@ -19,22 +18,22 @@ import {
19
18
  UpdateContactDto,
20
19
  } from '@onereach/types-contacts-api';
21
20
  import { CalApiParams, List } from '@or-sdk/base';
22
- import { BaseApi } from './baseApi';
23
21
  import BatchProcessApi from './batchProcessApi';
24
22
  import { getObjectSizeInBytes, chunkArrByMaxSize, debouncePromise } from '../utils';
25
23
  import { InitCreateBatchResponse, CreateContactsBatchResults } from '../types';
26
24
  import ContactBookApi from './contactBookApi';
27
- import { REQUEST_PAYLOAD_MAX_BYTES, FAILED_REQUEST_REPEATS, CONTACTS_DELETE_MAX_AMOUNT } from '../constants';
25
+ import { REQUEST_PAYLOAD_MAX_BYTES, CONTACTS_DELETE_MAX_AMOUNT } from '../constants';
28
26
  import { ApiError, CreateContactsBatchError } from '../apiError';
29
27
  import { DeleteContactsData } from '../types';
28
+ import BaseWithPoling from './baseWithPolingApi';
30
29
 
31
- export default class ContactApi extends BaseApi {
30
+ export default class ContactApi extends BaseWithPoling {
32
31
  constructor(
33
32
  protected readonly apiCall: <T>(params: CalApiParams) => Promise<T>,
34
- private batchProcessApi: BatchProcessApi,
33
+ protected batchProcessApi: BatchProcessApi,
35
34
  private bookServiceApi: ContactBookApi
36
35
  ) {
37
- super(apiCall);
36
+ super(apiCall, batchProcessApi);
38
37
  }
39
38
 
40
39
  private readonly apiBasePath = 'contact';
@@ -368,38 +367,4 @@ export default class ContactApi extends BaseApi {
368
367
  };
369
368
  }
370
369
 
371
- /**
372
- * Pols specific batch process until status of it isn't turned from 'pending to something else.
373
- */
374
- private async polling(batchId: string, repeats = 0): Promise<BatchProcessResponseDto> {
375
- let batchProcess: BatchProcessResponseDto;
376
-
377
- try {
378
- batchProcess = await debouncePromise(() => this.batchProcessApi.getBatchProcess(batchId), 1000);
379
- } catch (e) {
380
- if (
381
- repeats < FAILED_REQUEST_REPEATS
382
- && 'statusCode' in (e as object)
383
- && (e as unknown as ApiError).statusCode === 410
384
- ) {
385
- return await debouncePromise(() => this.polling(batchId, repeats + 1), 2000);
386
- }
387
- if (repeats < FAILED_REQUEST_REPEATS) {
388
- return this.polling(batchId, repeats + 1);
389
- }
390
- throw new CreateContactsBatchError((e as unknown as Error).message, batchId);
391
- }
392
-
393
- if (batchProcess.status === BatchProcessStatus.failed) {
394
- throw new CreateContactsBatchError(
395
- 'Could not complete batch process',
396
- batchId,
397
- batchProcess.messages
398
- );
399
- }
400
-
401
- return batchProcess.status === BatchProcessStatus.pending
402
- ? this.polling(batchId)
403
- : batchProcess;
404
- }
405
370
  }
package/src/types.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Token } from '@or-sdk/base';
2
2
  import {
3
3
  BatchProcessStatus,
4
+ BulkContactDto,
5
+ BulkCreateRequestDto,
4
6
  ContactBookParamsDto,
5
7
  CreateFieldSchemaDto,
6
8
  DeleteContactMultiParamsDto,
@@ -39,6 +41,12 @@ export type ContactsConfig = {
39
41
  * make many requests, for example for bulk operations.
40
42
  */
41
43
  withKeepAliveAgents?: boolean;
44
+
45
+ /**
46
+ * If true, the API error details like request url, params and body,
47
+ * will be outputted to console
48
+ */
49
+ withApiErrorLog?: boolean;
42
50
  };
43
51
 
44
52
  export interface ContactBookParams extends AdaptedListParams<ContactBookParamsDto> {
@@ -68,3 +76,11 @@ export type TrackBatchProcessResponse = {
68
76
  };
69
77
 
70
78
  export type CreateFieldSchemaParams = CreateFieldSchemaDto & {presetIds?: string[];};
79
+
80
+ export type BulkCreateData = Omit<BulkCreateRequestDto, 'batchId' | 'batchGroupId' | 'contacts'>
81
+ & { contacts: OptionalBy<BulkContactDto, 'contactKey'>[]; };
82
+
83
+ export type BatchBulkCreateData = Omit<BulkCreateRequestDto, 'batchId' | 'batchGroupId'>;
84
+
85
+ export type CreateBulkResults = {created: Record<string, string>; failed: Record<string, string>;};
86
+