@or-sdk/contacts 3.2.2-beta.1762.0 → 3.2.2-beta.1768.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 +164 -143
  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 +93 -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 +15 -7
  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 +3 -14
  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 +117 -127
  41. package/src/apiError.ts +0 -11
  42. package/src/constants.ts +2 -8
  43. package/src/types.ts +2 -17
  44. package/src/utils.ts +0 -16
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.2.2-beta.1762.0",
2
+ "version": "3.2.2-beta.1768.0",
3
3
  "name": "@or-sdk/contacts",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
@@ -24,7 +24,7 @@
24
24
  "access": "public"
25
25
  },
26
26
  "dependencies": {
27
- "@onereach/types-contacts-api": "4.7.2-beta.2230.0",
27
+ "@onereach/types-contacts-api": "4.7.2-beta.2237.0",
28
28
  "@or-sdk/base": "^0.28.3"
29
29
  }
30
30
  }
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,17 @@ 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';
24
+ import { ApiError } from '../apiError';
25
+ import { DeleteContactsData } from '../types';
26
26
 
27
27
  export default class ContactApi extends BaseApi {
28
28
  constructor(
29
29
  protected readonly apiCall: <T>(params: CalApiParams) => Promise<T>,
30
- private batchProcessApi: BatchProcessApi
30
+ private batchProcessApi: BatchProcessApi,
31
+ private bookServiceApi: ContactBookApi
31
32
  ) {
32
33
  super(apiCall);
33
34
  }
@@ -90,8 +91,8 @@ export default class ContactApi extends BaseApi {
90
91
  * @description Delete Contact
91
92
  * @param data
92
93
  */
93
- deleteMulti(data: DeleteContactMultiParamsDto): Promise<void> {
94
- if ((Array.isArray(data.ids) && data.ids.length > CONTACTS_DELETE_MAX_AMOUNT) || data.all) {
94
+ deleteMulti(data: DeleteContactsData): Promise<void> {
95
+ if ((Array.isArray(data.ids) && data.ids.length >= CONTACTS_DELETE_MAX_AMOUNT) || data.all) {
95
96
  return this.bulkDeleteContacts(data);
96
97
  }
97
98
  return this.apiCall({
@@ -101,6 +102,35 @@ export default class ContactApi extends BaseApi {
101
102
  });
102
103
  }
103
104
 
105
+ /**
106
+ * @description Delete all contacts from a book
107
+ */
108
+ async deleteContactsByBook(bookId: string): Promise<void> {
109
+ const contacts = await this.listContact({ contact_book: bookId });
110
+ return this.bulkDeleteContacts({
111
+ ids: contacts.items.map(({ id }) => id),
112
+ contact_book: bookId,
113
+ });
114
+ }
115
+
116
+ async initDeleteBookContactsBulk(bookId: string) {
117
+ const { contactsCount } = await this.bookServiceApi.getContactBook(bookId);
118
+ const contacts = await this.listContact({
119
+ contact_book: bookId,
120
+ size: CONTACTS_DELETE_MAX_AMOUNT,
121
+ });
122
+
123
+ const batchProcess = await this.runSingleDeleteContactsBulk({
124
+ ids: contacts.items.map(({ id }) => id),
125
+ contact_book: bookId,
126
+ });
127
+
128
+ return {
129
+ batchId: batchProcess.id,
130
+ totalBatches: Math.floor(contactsCount / 50),
131
+ };
132
+ }
133
+
104
134
  /**
105
135
  * @description Create Contact
106
136
  * @param data
@@ -115,61 +145,18 @@ export default class ContactApi extends BaseApi {
115
145
 
116
146
  /**
117
147
  * @description Create Contacts either in single or in multi batch(es), depending on payload size
148
+ * @param data
118
149
  */
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> {
150
+ async bulkCreateContacts(data: CreateMultipleContactsDto): Promise<ContactResponseDto[]> {
152
151
  const { contacts, ...rest } = data;
153
152
  const contactsMaxSize = REQUEST_PAYLOAD_MAX_BYTES - getObjectSizeInBytes({ ...rest });
154
153
  const contactsChunks = chunkArrByMaxSize(contacts, contactsMaxSize);
155
154
 
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
- };
155
+ return contactsChunks.length === 1
156
+ ? this.createContactsInSingleBatch(data)
157
+ : this.createContactsInMultiBatches(contactsChunks, rest);
170
158
  }
171
159
 
172
-
173
160
  /**
174
161
  * @description Merge two Contacts into one
175
162
  * @param id Contact id TO which the data will be merged
@@ -233,99 +220,102 @@ export default class ContactApi extends BaseApi {
233
220
  });
234
221
  }
235
222
 
236
- private async bulkDeleteContacts(data: DeleteContactMultiParamsDto): Promise<void> {
237
- const batchProcess = await this.apiCall<BatchProcessResponseDto>({
238
- method: 'DELETE',
239
- route: `${this.apiBasePath}/bulk`,
240
- data,
223
+ private async getSafelyContactsList(
224
+ contactIds: string[],
225
+ bookId: string | undefined,
226
+ repeats?: number
227
+ ): Promise<List<ContactResponseDto>> {
228
+ const contacts = await this.listContact({
229
+ contactIds,
230
+ ...(bookId && { contact_book: bookId }),
231
+ }).catch((e) => {
232
+ repeats = repeats || 0;
233
+ if (repeats < FAILED_REQUEST_REPEATS) {
234
+ return this.getSafelyContactsList(contactIds, bookId, repeats + 1);
235
+ }
236
+ throw new Error(e);
241
237
  });
242
- await this.polling(batchProcess.id);
238
+ return contacts;
243
239
  }
244
240
 
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;
241
+ private async bulkDeleteContacts(data: DeleteContactsData): Promise<void> {
242
+ const { ids, contact_book, all } = data;
243
+ if (all) {
244
+ if (!contact_book) {
245
+ throw new ApiError(400, 'contact_book should be provided in case if "all" is true');
269
246
  }
270
-
271
- const e = performance.now();
272
- // eslint-disable-next-line no-console
273
- console.log('awaiting ', promisesBatchSize, ' batches: ', (e - s) / 1000, 's');
247
+ return await this.deleteContactsByBook(contact_book);
248
+ }
249
+ if (!ids) {
250
+ throw new ApiError(400, 'Provide either "contact_book" or "all"');
274
251
  }
252
+ const batchSize = CONTACTS_DELETE_MAX_AMOUNT;
253
+ for (let i = 0;i < ids.length;i += batchSize) {
254
+ const chunk = ids.slice(i, i + batchSize);
255
+ await this.runSingleDeleteContactsBulk({
256
+ ids: chunk,
257
+ contact_book,
258
+ });
259
+ }
260
+ }
275
261
 
276
- return results;
262
+ private async runSingleDeleteContactsBulk(
263
+ data: DeleteContactMultiParamsDto,
264
+ withPoling = true
265
+ ): Promise<BatchProcessResponseDto> {
266
+ const batchProcess = await this.apiCall<BatchProcessResponseDto>({
267
+ method: 'DELETE',
268
+ route: `${this.apiBasePath}/bulk`,
269
+ data,
270
+ });
271
+ if (withPoling) {
272
+ const result = await this.polling(batchProcess.id);
273
+ return result;
274
+ }
275
+ return batchProcess;
277
276
  }
278
277
 
279
- private async createContactsInSingleBatch(data: CreateMultipleContactsDto): Promise<CreateContactsBatchResults> {
280
- const s = performance.now();
278
+ private async createContactsInSingleBatch(data: CreateMultipleContactsDto): Promise<ContactResponseDto[]> {
281
279
  const batchProcess = await this.apiCall<BatchProcessResponseDto>({
282
280
  method: 'POST',
283
281
  route: `${this.apiBasePath}/bulk`,
284
282
  data,
285
283
  });
286
284
  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
- };
285
+ const contactIds = batchProcessResult.results.flatMap<string>(i => JSON.parse(i));
286
+ const importedContacts = await this.getSafelyContactsList(contactIds, data.contact_book);
287
+ return importedContacts.items;
288
+ }
289
+
290
+ private async createContactsInMultiBatches(
291
+ contactsChunks: ContactRequestDto[][],
292
+ data: Omit<CreateMultipleContactsDto, 'contacts'>
293
+ ): Promise<ContactResponseDto[]> {
294
+ const batchPromises = contactsChunks.map((chunkContacts) => async () => this.createContactsInSingleBatch({
295
+ contacts: chunkContacts,
296
+ ...data,
297
+ }));
298
+
299
+ const results: ContactResponseDto[] = [];
300
+ for (const fn of batchPromises) {
301
+ const singleBatchResults = await fn();
302
+ results.push(...singleBatchResults);
303
+ }
304
+ return results;
294
305
  }
295
306
 
296
307
  /**
297
308
  * Pols specific batch process until status of it isn't turned from 'pending to something else.
298
309
  */
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) {
310
+ private async polling(batchId: string, repeats?: number): Promise<BatchProcessResponseDto> {
311
+ const batchProcessState = await this.batchProcessApi.getBatchProcess(batchId).catch((e) => {
312
+ repeats = repeats || 0;
311
313
  if (repeats < FAILED_REQUEST_REPEATS) {
312
314
  return this.polling(batchId, repeats + 1);
313
315
  }
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;
316
+ throw new Error(e);
317
+ });
318
+ // value 'pending' is used because of https://onereach.atlassian.net/browse/CU-562
319
+ return batchProcessState.status === 'pending' ? this.polling(batchId) : batchProcessState;
328
320
  }
329
321
  }
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, DeleteContactMultiParamsDto } from '@onereach/types-contacts-api';
3
3
  import { OrderOptions, PaginationOptions } from '@or-sdk/base';
4
4
  export * from '@onereach/types-contacts-api';
5
5
 
@@ -31,19 +31,4 @@ export interface ContactBookParams extends AdaptedListParams<ContactBookParamsDt
31
31
  export type AdaptedListParams<T extends ListApiParams & OrderParams> =
32
32
  Omit<T, 'order' | 'skip' | 'take'> & Partial<PaginationOptions & OrderOptions>;
33
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
- };
34
+ export type DeleteContactsData = DeleteContactMultiParamsDto & { all?: boolean;};
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
-