@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.
- package/CHANGELOG.md +9 -0
- package/dist/cjs/Contacts.js +14 -0
- package/dist/cjs/Contacts.js.map +1 -1
- package/dist/cjs/api/baseWithPolingApi.js +106 -0
- package/dist/cjs/api/baseWithPolingApi.js.map +1 -0
- package/dist/cjs/api/batchProcessApi.js +30 -0
- package/dist/cjs/api/batchProcessApi.js.map +1 -1
- package/dist/cjs/api/bulkContactsCreateApi.js +233 -0
- package/dist/cjs/api/bulkContactsCreateApi.js.map +1 -0
- package/dist/cjs/api/contactApi.js +6 -40
- package/dist/cjs/api/contactApi.js.map +1 -1
- package/dist/cjs/types.js.map +1 -1
- package/dist/esm/Contacts.js +14 -0
- package/dist/esm/Contacts.js.map +1 -1
- package/dist/esm/api/baseWithPolingApi.js +47 -0
- package/dist/esm/api/baseWithPolingApi.js.map +1 -0
- package/dist/esm/api/batchProcessApi.js +25 -1
- package/dist/esm/api/batchProcessApi.js.map +1 -1
- package/dist/esm/api/bulkContactsCreateApi.js +118 -0
- package/dist/esm/api/bulkContactsCreateApi.js.map +1 -0
- package/dist/esm/api/contactApi.js +4 -30
- package/dist/esm/api/contactApi.js.map +1 -1
- package/dist/esm/types.js.map +1 -1
- package/dist/types/Contacts.d.ts +4 -0
- package/dist/types/Contacts.d.ts.map +1 -1
- package/dist/types/api/baseWithPolingApi.d.ts +11 -0
- package/dist/types/api/baseWithPolingApi.d.ts.map +1 -0
- package/dist/types/api/batchProcessApi.d.ts +3 -0
- package/dist/types/api/batchProcessApi.d.ts.map +1 -1
- package/dist/types/api/bulkContactsCreateApi.d.ts +17 -0
- package/dist/types/api/bulkContactsCreateApi.d.ts.map +1 -0
- package/dist/types/api/contactApi.d.ts +3 -4
- package/dist/types/api/contactApi.d.ts.map +1 -1
- package/dist/types/types.d.ts +10 -1
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/Contacts.ts +17 -0
- package/src/api/baseWithPolingApi.ts +51 -0
- package/src/api/batchProcessApi.ts +25 -1
- package/src/api/bulkContactsCreateApi.ts +167 -0
- package/src/api/contactApi.ts +5 -40
- 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 {
|
|
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
|
+
}
|
package/src/api/contactApi.ts
CHANGED
|
@@ -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,
|
|
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
|
|
30
|
+
export default class ContactApi extends BaseWithPoling {
|
|
32
31
|
constructor(
|
|
33
32
|
protected readonly apiCall: <T>(params: CalApiParams) => Promise<T>,
|
|
34
|
-
|
|
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
|
+
|