@or-sdk/contacts 3.2.2-beta.1762.0 → 3.2.2-beta.1767.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 (44) hide show
  1. package/dist/cjs/Contacts.js +1 -1
  2. package/dist/cjs/Contacts.js.map +1 -1
  3. package/dist/cjs/api/batchProcessApi.js +0 -76
  4. package/dist/cjs/api/batchProcessApi.js.map +1 -1
  5. package/dist/cjs/api/contactApi.js +154 -144
  6. package/dist/cjs/api/contactApi.js.map +1 -1
  7. package/dist/cjs/apiError.js +1 -13
  8. package/dist/cjs/apiError.js.map +1 -1
  9. package/dist/cjs/constants.js +2 -2
  10. package/dist/cjs/constants.js.map +1 -1
  11. package/dist/cjs/utils.js +1 -11
  12. package/dist/cjs/utils.js.map +1 -1
  13. package/dist/esm/Contacts.js +1 -1
  14. package/dist/esm/Contacts.js.map +1 -1
  15. package/dist/esm/api/batchProcessApi.js +0 -31
  16. package/dist/esm/api/batchProcessApi.js.map +1 -1
  17. package/dist/esm/api/contactApi.js +83 -82
  18. package/dist/esm/api/contactApi.js.map +1 -1
  19. package/dist/esm/apiError.js +0 -8
  20. package/dist/esm/apiError.js.map +1 -1
  21. package/dist/esm/constants.js +2 -2
  22. package/dist/esm/constants.js.map +1 -1
  23. package/dist/esm/utils.js +0 -9
  24. package/dist/esm/utils.js.map +1 -1
  25. package/dist/types/api/batchProcessApi.d.ts +1 -4
  26. package/dist/types/api/batchProcessApi.d.ts.map +1 -1
  27. package/dist/types/api/contactApi.d.ts +12 -5
  28. package/dist/types/api/contactApi.d.ts.map +1 -1
  29. package/dist/types/apiError.d.ts +0 -6
  30. package/dist/types/apiError.d.ts.map +1 -1
  31. package/dist/types/constants.d.ts +2 -2
  32. package/dist/types/constants.d.ts.map +1 -1
  33. package/dist/types/types.d.ts +1 -15
  34. package/dist/types/types.d.ts.map +1 -1
  35. package/dist/types/utils.d.ts +0 -1
  36. package/dist/types/utils.d.ts.map +1 -1
  37. package/package.json +2 -2
  38. package/src/Contacts.ts +1 -1
  39. package/src/api/batchProcessApi.ts +3 -41
  40. package/src/api/contactApi.ts +104 -125
  41. package/src/apiError.ts +0 -11
  42. package/src/constants.ts +2 -8
  43. package/src/types.ts +1 -18
  44. package/src/utils.ts +0 -16
package/src/Contacts.ts CHANGED
@@ -52,7 +52,7 @@ export class Contacts extends Base {
52
52
  this.batchProcessApi = new BatchProcessApi(apiCall);
53
53
  this.contactBookApi = new ContactBookApi(apiCall);
54
54
  this.migrationsApi = new MigrationsApi(apiCall);
55
- this.contactApi = new ContactApi(apiCall, this.batchProcessApi);
55
+ this.contactApi = new ContactApi(apiCall, this.batchProcessApi, this.contactBookApi);
56
56
  this.fieldSchemaApi = new FieldSchemaApi(apiCall);
57
57
  this.schemaPresetApi = new SchemaPresetApi(apiCall);
58
58
  this.filterApi = new FilterApi(apiCall);
@@ -1,7 +1,8 @@
1
- import { TrackBatchProcessResponse } from '../types';
2
1
  import { BaseApi } from './baseApi';
3
2
 
4
- import { BatchProcessResponseDto, BatchProcessStatus, PendingBatchesResponseDto } from '@onereach/types-contacts-api';
3
+ import {
4
+ BatchProcessResponseDto,
5
+ } from '@onereach/types-contacts-api';
5
6
 
6
7
  export default class BatchProcessApi extends BaseApi {
7
8
  private readonly apiBasePath = 'batch-process';
@@ -12,43 +13,4 @@ export default class BatchProcessApi extends BaseApi {
12
13
  route: `${this.apiBasePath}/${id}`,
13
14
  });
14
15
  }
15
-
16
- /**
17
- * Returns the count of pending batch processes and their ids.
18
- */
19
- getPendingBatchProcesses(): Promise<PendingBatchesResponseDto> {
20
- return this.apiCall({
21
- method: 'GET',
22
- route: `${this.apiBasePath}/pending-batches`,
23
- });
24
- }
25
-
26
- /**
27
- * Provides information about create contacts batch process: its status, errors if
28
- * the status is failed, and created contacts ids if the status is success.
29
- */
30
- async trackBatchProcess(id: string): Promise<TrackBatchProcessResponse> {
31
- const batchProcess = await this.getBatchProcess(id);
32
-
33
- let result: TrackBatchProcessResponse = {
34
- status: batchProcess.status,
35
- };
36
-
37
- if (batchProcess.status === BatchProcessStatus.success) {
38
- result = {
39
- ...result,
40
- contactsIds: batchProcess.results.flatMap<string>(i => JSON.parse(i)),
41
- };
42
- }
43
-
44
- if (batchProcess.status === BatchProcessStatus.failed) {
45
- result = {
46
- ...result,
47
- errors: batchProcess.messages,
48
- };
49
- }
50
-
51
- return result;
52
- }
53
-
54
16
  }
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  BatchProcessResponseDto,
3
- BatchProcessStatus,
4
3
  ContactParamsDto,
5
4
  ContactRequestDto,
6
5
  ContactResponseDto,
@@ -19,15 +18,15 @@ import {
19
18
  import { CalApiParams, List } from '@or-sdk/base';
20
19
  import { BaseApi } from './baseApi';
21
20
  import BatchProcessApi from './batchProcessApi';
22
- import { getObjectSizeInBytes, chunkArrByMaxSize, debouncePromise } from '../utils';
23
- import { InitCreateBatchResponse, CreateContactsBatchResults } from '../types';
21
+ import ContactBookApi from './contactBookApi';
22
+ import { getObjectSizeInBytes, chunkArrByMaxSize } from '../utils';
24
23
  import { REQUEST_PAYLOAD_MAX_BYTES, FAILED_REQUEST_REPEATS, CONTACTS_DELETE_MAX_AMOUNT } from '../constants';
25
- import { CreateContactsBatchError } from '../apiError';
26
24
 
27
25
  export default class ContactApi extends BaseApi {
28
26
  constructor(
29
27
  protected readonly apiCall: <T>(params: CalApiParams) => Promise<T>,
30
- private batchProcessApi: BatchProcessApi
28
+ private batchProcessApi: BatchProcessApi,
29
+ private bookServiceApi: ContactBookApi
31
30
  ) {
32
31
  super(apiCall);
33
32
  }
@@ -91,7 +90,7 @@ export default class ContactApi extends BaseApi {
91
90
  * @param data
92
91
  */
93
92
  deleteMulti(data: DeleteContactMultiParamsDto): Promise<void> {
94
- if ((Array.isArray(data.ids) && data.ids.length > CONTACTS_DELETE_MAX_AMOUNT) || data.all) {
93
+ if ((Array.isArray(data.ids) && data.ids.length >= CONTACTS_DELETE_MAX_AMOUNT) || data.all) {
95
94
  return this.bulkDeleteContacts(data);
96
95
  }
97
96
  return this.apiCall({
@@ -101,6 +100,35 @@ export default class ContactApi extends BaseApi {
101
100
  });
102
101
  }
103
102
 
103
+ /**
104
+ * @description Delete all contacts from a book
105
+ */
106
+ async deleteContactsByBook(bookId: string): Promise<void> {
107
+ const contacts = await this.listContact({ contact_book: bookId });
108
+ return this.bulkDeleteContacts({
109
+ ids: contacts.items.map(({ id }) => id),
110
+ contact_book: bookId,
111
+ });
112
+ }
113
+
114
+ async initDeleteBookContactsBulk(bookId: string) {
115
+ const { contactsCount } = await this.bookServiceApi.getContactBook(bookId);
116
+ const contacts = await this.listContact({
117
+ contact_book: bookId,
118
+ size: CONTACTS_DELETE_MAX_AMOUNT,
119
+ });
120
+
121
+ const batchProcess = await this.runSingleDeleteContactsBulk({
122
+ ids: contacts.items.map(({ id }) => id),
123
+ contact_book: bookId,
124
+ });
125
+
126
+ return {
127
+ batchId: batchProcess.id,
128
+ totalBatches: Math.floor(contactsCount / 50),
129
+ };
130
+ }
131
+
104
132
  /**
105
133
  * @description Create Contact
106
134
  * @param data
@@ -115,61 +143,18 @@ export default class ContactApi extends BaseApi {
115
143
 
116
144
  /**
117
145
  * @description Create Contacts either in single or in multi batch(es), depending on payload size
146
+ * @param data
118
147
  */
119
- async bulkCreateContacts(data: CreateMultipleContactsDto): Promise<string[]> {
120
- // eslint-disable-next-line no-console
121
- console.log('contacts: ', data.contacts.length);
122
- const { contacts, ...rest } = data;
123
- const contactsMaxSize = REQUEST_PAYLOAD_MAX_BYTES - getObjectSizeInBytes({ ...rest });
124
- const contactsChunks = chunkArrByMaxSize(contacts, contactsMaxSize);
125
-
126
- contactsChunks.forEach((chunk, idx) => {
127
- // eslint-disable-next-line no-console
128
- console.log('size of ', idx, ' chunk', ' is ', getObjectSizeInBytes(chunk));
129
- });
130
- // eslint-disable-next-line no-console
131
- console.log('chunks: ', contactsChunks.length);
132
- const results = contactsChunks.length === 1
133
- ? [await this.createContactsInSingleBatch(data)]
134
- : await this.createContactsInMultiBatches(contactsChunks, rest);
135
-
136
- return results.reduce<string[]>((acc, { contactsIds }) => ([...acc, ...contactsIds]), []);
137
- }
138
-
139
- /**
140
- * Breaks the contacts to chunks of maximum allowed size (200 KB) and launches create batch
141
- * only for the first chunk.
142
- * Returns batch id in response by which the batch can be tracked using batch process API.
143
- * Note: since create contact batch is a very expensive operations from terms of consuming
144
- * DB connections, it is highly recommended to check amount of pending (running) batches
145
- * prior to this method execution, and if the amount is greater of 2 the batch initiation
146
- * should be deferred.
147
- *
148
- * @see batchProcessApi.trackBatchProcess
149
- * @see batchProcessApi.getPendingBatchProcesses
150
- */
151
- async initCreateBatch(data: CreateMultipleContactsDto): Promise<InitCreateBatchResponse> {
148
+ async bulkCreateContacts(data: CreateMultipleContactsDto): Promise<ContactResponseDto[]> {
152
149
  const { contacts, ...rest } = data;
153
150
  const contactsMaxSize = REQUEST_PAYLOAD_MAX_BYTES - getObjectSizeInBytes({ ...rest });
154
151
  const contactsChunks = chunkArrByMaxSize(contacts, contactsMaxSize);
155
152
 
156
- const batchProcess = await this.apiCall<BatchProcessResponseDto>({
157
- method: 'POST',
158
- route: `${this.apiBasePath}/bulk`,
159
- data: {
160
- contacts: contactsChunks[0],
161
- ...rest,
162
- },
163
- });
164
-
165
- return {
166
- totalChunks: contactsChunks.length,
167
- firstChunkSize: contactsChunks[0].length,
168
- batchId: batchProcess.id,
169
- };
153
+ return contactsChunks.length === 1
154
+ ? this.createContactsInSingleBatch(data)
155
+ : this.createContactsInMultiBatches(contactsChunks, rest);
170
156
  }
171
157
 
172
-
173
158
  /**
174
159
  * @description Merge two Contacts into one
175
160
  * @param id Contact id TO which the data will be merged
@@ -233,99 +218,93 @@ export default class ContactApi extends BaseApi {
233
218
  });
234
219
  }
235
220
 
221
+ private async getSafelyContactsList(
222
+ contactIds: string[],
223
+ bookId: string | undefined,
224
+ repeats?: number
225
+ ): Promise<List<ContactResponseDto>> {
226
+ const contacts = await this.listContact({
227
+ contactIds,
228
+ ...(bookId && { contact_book: bookId }),
229
+ }).catch((e) => {
230
+ repeats = repeats || 0;
231
+ if (repeats < FAILED_REQUEST_REPEATS) {
232
+ return this.getSafelyContactsList(contactIds, bookId, repeats + 1);
233
+ }
234
+ throw new Error(e);
235
+ });
236
+ return contacts;
237
+ }
238
+
236
239
  private async bulkDeleteContacts(data: DeleteContactMultiParamsDto): Promise<void> {
240
+ const { ids = [], contact_book = '' } = data;
241
+ const batchSize = CONTACTS_DELETE_MAX_AMOUNT;
242
+ for (let i = 0;i < ids.length;i += batchSize) {
243
+ const chunk = ids.slice(i, i + batchSize);
244
+ await this.runSingleDeleteContactsBulk({
245
+ ids: chunk,
246
+ contact_book,
247
+ });
248
+ }
249
+ }
250
+
251
+ private async runSingleDeleteContactsBulk(
252
+ data: DeleteContactMultiParamsDto,
253
+ withPoling = true
254
+ ): Promise<BatchProcessResponseDto> {
237
255
  const batchProcess = await this.apiCall<BatchProcessResponseDto>({
238
256
  method: 'DELETE',
239
257
  route: `${this.apiBasePath}/bulk`,
240
258
  data,
241
259
  });
242
- await this.polling(batchProcess.id);
243
- }
244
-
245
- private async createContactsInMultiBatches(
246
- contactsChunks: ContactRequestDto[][],
247
- data: Omit<CreateMultipleContactsDto, 'contacts'>
248
- ): Promise<CreateContactsBatchResults[]> {
249
- const batchPromises = contactsChunks
250
- .map((chunkContacts) => async () => this.createContactsInSingleBatch({
251
- contacts: chunkContacts,
252
- ...data,
253
- }));
254
-
255
- const promisesBatchSize = 2;
256
- const results: CreateContactsBatchResults[] = [];
257
-
258
- for (let i = 0;i < batchPromises.length;i += promisesBatchSize) {
259
- const s = performance.now();
260
- try {
261
- const chunk = batchPromises.slice(i, i + promisesBatchSize);
262
- const chunkResults = await Promise.all(chunk.map((p) => p()));
263
- results.push(...chunkResults);
264
- } catch (e) {
265
- if (e instanceof CreateContactsBatchError) {
266
- e.processedBatchIds = results.map(({ batchId }) => batchId);
267
- }
268
- throw e;
269
- }
270
-
271
- const e = performance.now();
272
- // eslint-disable-next-line no-console
273
- console.log('awaiting ', promisesBatchSize, ' batches: ', (e - s) / 1000, 's');
260
+ if (withPoling) {
261
+ const result = await this.polling(batchProcess.id);
262
+ return result;
274
263
  }
275
-
276
- return results;
264
+ return batchProcess;
277
265
  }
278
266
 
279
- private async createContactsInSingleBatch(data: CreateMultipleContactsDto): Promise<CreateContactsBatchResults> {
280
- const s = performance.now();
267
+ private async createContactsInSingleBatch(data: CreateMultipleContactsDto): Promise<ContactResponseDto[]> {
281
268
  const batchProcess = await this.apiCall<BatchProcessResponseDto>({
282
269
  method: 'POST',
283
270
  route: `${this.apiBasePath}/bulk`,
284
271
  data,
285
272
  });
286
273
  const batchProcessResult = await this.polling(batchProcess.id);
287
- const e = performance.now();
288
- // eslint-disable-next-line no-console
289
- console.log('single batch time: ', (e - s) / 1000, 's');
290
- return {
291
- batchId: batchProcess.id,
292
- contactsIds: batchProcessResult.results.flatMap<string>(i => JSON.parse(i)),
293
- };
274
+ const contactIds = batchProcessResult.results.flatMap<string>(i => JSON.parse(i));
275
+ const importedContacts = await this.getSafelyContactsList(contactIds, data.contact_book);
276
+ return importedContacts.items;
277
+ }
278
+
279
+ private async createContactsInMultiBatches(
280
+ contactsChunks: ContactRequestDto[][],
281
+ data: Omit<CreateMultipleContactsDto, 'contacts'>
282
+ ): Promise<ContactResponseDto[]> {
283
+ const batchPromises = contactsChunks.map((chunkContacts) => async () => this.createContactsInSingleBatch({
284
+ contacts: chunkContacts,
285
+ ...data,
286
+ }));
287
+
288
+ const results: ContactResponseDto[] = [];
289
+ for (const fn of batchPromises) {
290
+ const singleBatchResults = await fn();
291
+ results.push(...singleBatchResults);
292
+ }
293
+ return results;
294
294
  }
295
295
 
296
296
  /**
297
297
  * Pols specific batch process until status of it isn't turned from 'pending to something else.
298
298
  */
299
- private async polling(batchId: string, repeats = 0): Promise<BatchProcessResponseDto> {
300
-
301
- let batchProcess: BatchProcessResponseDto;
302
- try {
303
- batchProcess = await debouncePromise(() => this.batchProcessApi.getBatchProcess(batchId), 1000);
304
- // eslint-disable-next-line no-console
305
- const counter = batchCounters[batchId] || 0;
306
- // eslint-disable-next-line no-console
307
- console.log('polling counter of ', batchId, ' = ', counter, ' | status = ', batchProcess.status);
308
- batchCounters[batchId] = counter + 1;
309
-
310
- } catch (e) {
299
+ private async polling(batchId: string, repeats?: number): Promise<BatchProcessResponseDto> {
300
+ const batchProcessState = await this.batchProcessApi.getBatchProcess(batchId).catch((e) => {
301
+ repeats = repeats || 0;
311
302
  if (repeats < FAILED_REQUEST_REPEATS) {
312
303
  return this.polling(batchId, repeats + 1);
313
304
  }
314
- throw new CreateContactsBatchError((e as unknown as Error).message, batchId);
315
- }
316
-
317
- if (batchProcess.status === BatchProcessStatus.failed) {
318
- throw new CreateContactsBatchError(
319
- 'Could not complete batch process',
320
- batchId,
321
- batchProcess.messages
322
- );
323
- }
324
-
325
- return batchProcess.status === BatchProcessStatus.pending
326
- ? this.polling(batchId)
327
- : batchProcess;
305
+ throw new Error(e);
306
+ });
307
+ // value 'pending' is used because of https://onereach.atlassian.net/browse/CU-562
308
+ return batchProcessState.status === 'pending' ? this.polling(batchId) : batchProcessState;
328
309
  }
329
310
  }
330
-
331
- const batchCounters: Record<string, number> = {};
package/src/apiError.ts CHANGED
@@ -11,14 +11,3 @@ export class ApiError extends Error {
11
11
  super(message);
12
12
  }
13
13
  }
14
-
15
- export class CreateContactsBatchError extends Error {
16
- constructor(
17
- message: string,
18
- public readonly failedBatchId: string,
19
- public readonly batchMessages?: string[],
20
- public processedBatchIds?: string[],
21
- ) {
22
- super(message);
23
- }
24
- }
package/src/constants.ts CHANGED
@@ -1,13 +1,7 @@
1
1
  export const CONTACTS_SERVICE_KEY = 'contacts-api';
2
2
 
3
- export const REQUEST_PAYLOAD_MAX_BYTES = 150000;
3
+ export const REQUEST_PAYLOAD_MAX_BYTES = 128000;
4
4
 
5
5
  export const FAILED_REQUEST_REPEATS = 3;
6
6
 
7
- /**
8
- * 5 selected experimentally
9
- * Since contacts deleting assumes cascading on related field values and deleting
10
- * relations in contact_book_contact table, it results in huge amount of queries
11
- * which sent simultaneously might kill connection to the DB.
12
- */
13
- export const CONTACTS_DELETE_MAX_AMOUNT = 5;
7
+ export const CONTACTS_DELETE_MAX_AMOUNT = 50;
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Token } from '@or-sdk/base';
2
- import { BatchProcessStatus, ContactBookParamsDto, ListApiParams, OrderParams } from '@onereach/types-contacts-api';
2
+ import { ContactBookParamsDto, ListApiParams, OrderParams } from '@onereach/types-contacts-api';
3
3
  import { OrderOptions, PaginationOptions } from '@or-sdk/base';
4
4
  export * from '@onereach/types-contacts-api';
5
5
 
@@ -30,20 +30,3 @@ export interface ContactBookParams extends AdaptedListParams<ContactBookParamsDt
30
30
 
31
31
  export type AdaptedListParams<T extends ListApiParams & OrderParams> =
32
32
  Omit<T, 'order' | 'skip' | 'take'> & Partial<PaginationOptions & OrderOptions>;
33
-
34
- export type CreateContactsBatchResults ={
35
- batchId: string;
36
- contactsIds: string[];
37
- };
38
-
39
- export type InitCreateBatchResponse = {
40
- totalChunks: number;
41
- firstChunkSize: number;
42
- batchId: string;
43
- };
44
-
45
- export type TrackBatchProcessResponse = {
46
- status: BatchProcessStatus;
47
- errors?: string[];
48
- contactsIds?: string[];
49
- };
package/src/utils.ts CHANGED
@@ -43,19 +43,3 @@ export const chunkArrByMaxSize = <T>(arr: T[], maxSize: number): T[][] => {
43
43
  ...chunkArrByMaxSize(arr.slice(mid), maxSize),
44
44
  ];
45
45
  };
46
-
47
- export function debouncePromise<T>(
48
- caller: () => Promise<T>,
49
- delay: number,
50
- reject?: (value: string | T | PromiseLike<T>) => void
51
- ): Promise<T> {
52
- let timeout: NodeJS.Timeout | null = null;
53
-
54
- return new Promise<T>((res) => {
55
- if (timeout) {
56
- clearTimeout(timeout);
57
- }
58
- timeout = setTimeout(() => caller().then((x) => res(x)).catch((err) => reject && reject(err)), delay);
59
- });
60
- }
61
-