@devrev/ts-adaas 1.2.4 → 1.2.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/README.md CHANGED
@@ -1,235 +1,124 @@
1
- # ADaaS Library
2
-
3
- ## Release Notes
4
-
5
- ### v1.2.4
6
-
7
- - Do not fail the extraction of attachments if streaming of single attachment fails.
8
-
9
- ### v1.2.3
10
-
11
- - Add `local` flag to use for local development of the ADaaS snap-ins.
12
- - Send library version, snap-in version and snap-in slug in headers while emitting.
13
- - Make `actor_id` field optional for `SsorAttachment` interface.
14
- - Fix bugs related to event handling, error logging.
15
-
16
- ### v1.2.2
17
-
18
- - Add library version as a part of control protocol.
19
- - Improve axios client and adapter logging.
20
- - Fix bugs related to state handling.
21
-
22
- ### v1.2.1
23
-
24
- - Reduced the `delayFactor` to minimize unnecessary delays.
25
- - Correct the setting of the `lastSyncStarted` timestamp.
26
- - Improve logging for attachment extraction and loading.
27
- - Fix several bugs related to the control protocol.
28
-
29
- ### v1.2.0
30
-
31
- - Add support for loading attachments from DevRev to external system.
32
-
33
- ### v1.1.6
34
-
35
- - Add exponential retry and handle rate-limiting towards DevRev.
36
- - Gracefully handle failure to upload extracted attachments.
37
-
38
- ### v1.1.5
39
-
40
- - Increase `delayFactor` and number of retries for the exponential backoff retry mechanism for HTTP requests.
41
- - Provide an inject function for streaming attachments.
42
- - Fix the attachments streaming bug.
43
-
44
- ### v1.1.4
45
-
46
- - Provide log lines and stack traces for runtime worker errors.
47
-
48
- ### v1.1.3
49
-
50
- - Export `axios` and `axiosClient` with the exponential backoff retry mechanism for HTTP requests and omit Authorization headers from Axios errors.
51
- - Resolve circular structure logging issues.
52
- - Fix the attachments metadata normalization bug.
53
- - Improve repository logging.
54
-
55
- ### v1.1.2
56
-
57
- - Unify incoming and outgoing event context.
58
- - Add `dev_oid` to logger tags.
59
-
60
- ### v1.1.1
61
-
62
- - Add default workers for loading deletion events.
63
-
64
- ### v1.1.0
65
-
66
- - Support sync from DevRev to the external system. (Known limitations: no support for loading attachments.)
67
-
68
- ### v1.0.4
69
-
70
- - Fix logging from worker threads.
71
-
72
- ### v1.0.3
73
-
74
- - Add release notes.
75
-
76
- ### v1.0.2
77
-
78
- - Fix bugs and improve local development.
79
- - Expose `formatAxiosError` function for error handling.
80
-
81
- ### v1.0.1
82
-
83
- - Fix bugs and improve logging.
84
-
85
- ### v1.0.0
86
-
87
- - Enable extractions to use the full lambda runtime and gracefully handle execution context timeout.
88
- - Simplify metadata and data normalization and uploading with the repo implementation.
89
- - Provide default handling of the attachment extraction phase in the ADaaS SDK library.
90
- - Reduce file size and streamline processes with gzip compression.
91
- - Fix bugs and improve error handling.
92
-
93
- ### v0.0.3
94
-
95
- - Support new recipe management.
96
-
97
- ### v0.0.2
98
-
99
- - Support the State API.
100
- - Provide an HTTP client for API requests.
101
- - Create local artifact files in the local development environment.
102
- - Improve logging.
103
-
104
- ### v0.0.1
105
-
106
- - Implement a demo of the ADaaS snap-in.
107
- - Add an adapter for the ADaaS control protocol with helper functions.
108
- - Provide an uploader for uploading artifacts.
109
-
110
- # Overview
1
+ # Airdrop SDK
111
2
 
112
3
  [![Coverage Status](https://coveralls.io/repos/github/devrev/adaas-sdk/badge.svg?branch=main&t=s4Otlm)](https://coveralls.io/github/devrev/adaas-sdk?branch=main)
113
4
 
114
- The ADaaS (Airdrop-as-a-Service) Library for TypeScript helps developers build Snap-ins that integrate with DevRev’s ADaaS platform. This library simplifies the workflow for handling data extraction and loading, event-driven actions, state management, and artifact handling.
5
+ ## Overview
6
+
7
+ The Airdrop SDK for TypeScript helps developers build snap-ins that integrate with DevRev’s Airdrop platform.
8
+ This SDK simplifies the workflow for handling data extraction and loading, event-driven actions, state management, and artifact handling.
115
9
 
116
10
  It provides features such as:
117
11
 
118
- - Type Definitions: Structured types for ADaaS control protocol
12
+ - Type Definitions: Structured types for Airdrop control protocol
119
13
  - Event Management: Easily emit events for different extraction or loading phases
120
14
  - State Handling: Update and access state in real-time within tasks
121
15
  - Artifact Management: Supports batched storage of artifacts
122
16
  - Error & Timeout Support: Error handling and timeout management for long-running tasks
123
17
 
124
- # Installation
18
+ ## Installation
125
19
 
126
20
  ```bash
127
21
  npm install @devrev/ts-adaas
128
22
  ```
129
23
 
130
- # Usage
24
+ ## Reference
131
25
 
132
- ADaaS Snap-ins can import data in both directions: from external sources to DevRev and from DevRev to external sources. Both directions are composed of several phases.
26
+ ### `spawn` function
133
27
 
134
- From external source to DevRev:
28
+ This function initializes a new worker thread and oversees its lifecycle.
29
+ It should be invoked when the snap-in receives a message from the Airdrop platform.
30
+ The worker script provided then handles the event accordingly.
135
31
 
136
- - External Sync Units Extraction
137
- - Metadata Extraction
138
- - Data Extraction
139
- - Attachments Extraction
32
+ #### Usage
140
33
 
141
- From DevRev to external source:
34
+ ```typescript
35
+ spawn({ event, initialState, workerPath, options })
36
+ ```
142
37
 
143
- - Data Loading
38
+ #### Parameters
144
39
 
145
- Each phase comes with unique requirements for processing task, and both timeout and error handling.
40
+ * _event_
41
+
42
+ Required. An object of type __AirdropEvent__ that is received from the Airdrop platform.
146
43
 
147
- The ADaaS library exports processTask to structure the work within each phase, and onTimeout function to handle timeouts.
44
+ * _initialState_
148
45
 
149
- ### ADaaS Snap-in Invocation
46
+ Required. Object of __any__ type that represents the initial state of the snap-in.
150
47
 
151
- Each ADaaS snap-in must handle all the phases of ADaaS extraction. In a Snap-in, you typically define a `run` function that iterates over events and invokes workers per extraction phase.
48
+ * _workerPath_
152
49
 
153
- ```typescript
154
- import { AirdropEvent, EventType, spawn } from '@devrev/ts-adaas';
50
+ Required. A __string__ that represents the path to the worker file.
155
51
 
156
- interface DummyExtractorState {
157
- issues: { completed: boolean };
158
- users: { completed: boolean };
159
- attachments: { completed: boolean };
160
- }
52
+ * _options_
161
53
 
162
- const initialState: DummyExtractorState = {
163
- issues: { completed: false },
164
- users: { completed: false },
165
- attachments: { completed: false },
166
- };
54
+ Optional. An object of type **WorkerAdapterOptions**, which will be passed to the newly created worker. This worker will then initialize a `WorkerAdapter` by invoking the `processTask` function. The options include:
55
+
56
+ * `isLocalDevelopment`
57
+
58
+ A __boolean__ flag. If set to `true`, intermediary files containing extracted data will be stored on the local machine, which is useful during development. The default value is `false`.
167
59
 
168
- function getWorkerPerExtractionPhase(event: AirdropEvent) {
169
- let path;
170
- switch (event.payload.event_type) {
171
- case EventType.ExtractionExternalSyncUnitsStart:
172
- path = __dirname + '/workers/external-sync-units-extraction';
173
- break;
174
- case EventType.ExtractionMetadataStart:
175
- path = __dirname + '/workers/metadata-extraction';
176
- break;
177
- case EventType.ExtractionDataStart:
178
- case EventType.ExtractionDataContinue:
179
- path = __dirname + '/workers/data-extraction';
180
- break;
181
- }
182
- return path;
183
- }
60
+ * `timeout`
61
+
62
+ A __number__ that specifies the timeout duration for the lambda function, in milliseconds. The default is 10 minutes (10 * 60 * 1000 milliseconds), with a maximum allowable duration of 13 minutes (13 * 60 * 1000 milliseconds).
63
+
64
+ * `batchSize`
65
+
66
+ A __number__ that determines the maximum number of items to be processed and saved to an intermediary file before being sent to the Airdrop platform. The default batch size is 2,000.
184
67
 
68
+ #### Return value
69
+
70
+ A __promise__ that resolves once the worker has completed processing.
71
+
72
+ #### Example
73
+
74
+ ```typescript
185
75
  const run = async (events: AirdropEvent[]) => {
186
76
  for (const event of events) {
187
77
  const file = getWorkerPerExtractionPhase(event);
188
- await spawn<DummyExtractorState>({
78
+ await spawn<ExtractorState>({
189
79
  event,
190
80
  initialState,
191
81
  workerPath: file,
192
- options: {
193
- isLocalDevelopment: true,
194
- },
195
82
  });
196
83
  }
197
84
  };
198
-
199
- export default run;
200
85
  ```
201
86
 
202
- ## Extraction
87
+ ### `processTask` function
203
88
 
204
- The ADaaS snap-in extraction lifecycle consists of three main phases: External Sync Units Extraction, Metadata Extraction, and Data Extraction. Each phase is defined in a separate file and is responsible for fetching the respective data.
89
+ The `processTask` function retrieves the current state from the Airdrop platform and initializes a new `WorkerAdapter`.
90
+ It executes the code specified in the `task` parameter, which contains the worker's functionality.
91
+ If a timeout occurs, the function handles it by executing the `onTimeout` callback, ensuring the worker exits gracefully.
92
+ Both functions receive an `adapter` parameter, representing the initialized `WorkerAdapter` object.
205
93
 
206
- The ADaaS library provides a repository management system to handle artifacts in batches. The `initializeRepos` function initializes the repositories, and the `push` function uploads the artifacts to the repositories. The `postState` function is used to post the state of the extraction task.
207
94
 
208
- State management is crucial for ADaaS Snap-ins to maintain the state of the extraction task. The `postState` function is used to post the state of the extraction task. The state is stored in the adapter and can be retrieved using the `adapter.state` property.
95
+ #### Usage
96
+ ```typescript
97
+ processTask({ task, onTimeout })
98
+ ```
209
99
 
210
- ### 1. External Sync Units Extraction
100
+ #### Parameters
211
101
 
212
- This phase is defined in `external-sync-units-extraction.ts` and is responsible for fetching the external sync units.
102
+ * _task_
103
+
104
+ Required. A __function__ that defines the logic associated with the given event type.
213
105
 
214
- ```typescript
215
- import {
216
- ExternalSyncUnit,
217
- ExtractorEventType,
218
- processTask,
219
- } from '@devrev/ts-adaas';
106
+ * _onTimeout_
107
+
108
+ Required. A __function__ managing the timeout of the lambda invocation, including saving any necessary progress at the time of timeout.
220
109
 
221
- const externalSyncUnits: ExternalSyncUnit[] = [
222
- {
223
- id: 'devrev',
224
- name: 'devrev',
225
- description: 'Demo external sync unit',
226
- item_count: 2,
227
- item_type: 'issues',
228
- },
229
- ];
110
+ #### Example
230
111
 
112
+ ````typescript
113
+ // External sync units extraction
231
114
  processTask({
232
115
  task: async ({ adapter }) => {
116
+ const httpClient = new HttpClient(adapter.event);
117
+
118
+ const todoLists = await httpClient.getTodoLists();
119
+
120
+ const externalSyncUnits: ExternalSyncUnit[] = todoLists.map((todoList) => normalizeTodoList(todoList));
121
+
233
122
  await adapter.emit(ExtractorEventType.ExtractionExternalSyncUnitsDone, {
234
123
  external_sync_units: externalSyncUnits,
235
124
  });
@@ -242,170 +131,167 @@ processTask({
242
131
  });
243
132
  },
244
133
  });
245
- ```
134
+ ````
135
+
136
+ ### `WorkerAdapter` class
246
137
 
247
- ### 2. Metadata Extraction
138
+ Used to interact with Airdrop platform.
139
+ Provides utilities to emit events to the Airdrop platform, update the state of the snap-in and upload artifacts (files with data) to the platform.
248
140
 
249
- This phase is defined in `metadata-extraction.ts` and is responsible for fetching the metadata.
141
+ ### Usage
250
142
 
251
143
  ```typescript
252
- import { ExtractorEventType, processTask } from '@devrev/ts-adaas';
253
- import externalDomainMetadata from '../dummy-extractor/external_domain_metadata.json';
144
+ new WorkerAdapter({
145
+ event,
146
+ adapterState,
147
+ options,
148
+ });
149
+ ```
254
150
 
255
- const repos = [{ itemType: 'external_domain_metadata' }];
151
+ #### Parameters
256
152
 
257
- processTask({
258
- task: async ({ adapter }) => {
259
- adapter.initializeRepos(repos);
260
- await adapter
261
- .getRepo('external_domain_metadata')
262
- ?.push([externalDomainMetadata]);
263
- await adapter.emit(ExtractorEventType.ExtractionMetadataDone);
264
- },
265
- onTimeout: async ({ adapter }) => {
266
- await adapter.emit(ExtractorEventType.ExtractionMetadataError, {
267
- error: { message: 'Failed to extract metadata. Lambda timeout.' },
268
- });
269
- },
153
+ * _event_
154
+
155
+ Required. An object of type __AirdropEvent__ that is received from the Airdrop platform.
156
+
157
+ * _adapterState_
158
+
159
+ Required. An object of type __State__, which represents the initial state of the adapter.
160
+
161
+ * _options_
162
+
163
+ Optional. An object of type __WorkerAdapterOptions__ that specifies additional configuration options for the `WorkerAdapter`. This object is passed via the `spawn` function.
164
+
165
+ #### Example
166
+
167
+ ```typescript
168
+ const adapter = new WorkerAdapter<ConnectorState>({
169
+ event,
170
+ adapterState,
171
+ options,
270
172
  });
271
173
  ```
272
174
 
273
- ### 3. Data Extraction
175
+ ### `WorkerAdapter.state` property
274
176
 
275
- This phase is defined in `data-extraction.ts` and is responsible for fetching the data. In this phase also attachments metadata is extracted.
177
+ Getter and setter methods for working with the adapter state.
178
+
179
+ ### Usage
276
180
 
277
181
  ```typescript
278
- import { EventType, ExtractorEventType, processTask } from '@devrev/ts-adaas';
279
- import { normalizeAttachment, normalizeIssue, normalizeUser } from '../dummy-extractor/data-normalization';
182
+ // get state
183
+ const adapterState = adapter.state;
280
184
 
281
- const issues = [
282
- { id: 'issue-1', created_date: '1999-12-25T01:00:03+01:00', ... },
283
- { id: 'issue-2', created_date: '1999-12-27T15:31:34+01:00', ... },
284
- ];
185
+ // set state
186
+ adapter.state = newAdapterState;
187
+ ```
285
188
 
286
- const users = [
287
- { id: 'user-1', created_date: '1999-12-25T01:00:03+01:00', ... },
288
- { id: 'user-2', created_date: '1999-12-27T15:31:34+01:00', ... },
289
- ];
189
+ #### Example
290
190
 
291
- const attachments = [
292
- { url: 'https://app.dev.devrev-eng.ai/favicon.ico', id: 'attachment-1', ... },
293
- { url: 'https://app.dev.devrev-eng.ai/favicon.ico', id: 'attachment-2', ... },
294
- ];
191
+ ```typescript
192
+ export const initialState: ExtractorState = {
193
+ users: { completed: false },
194
+ tasks: { completed: false },
195
+ attachments: { completed: false },
196
+ };
197
+
198
+ adapter.state = initialState;
199
+ ```
200
+
201
+ ### `WorkerAdapter.initializeRepos` method
202
+
203
+ Initializes a `Repo` object for each item provided.
204
+
205
+ ### Usage
206
+
207
+ ```typescript
208
+ adapter.initializeRepos(repos);
209
+ ```
210
+
211
+ #### Parameters
212
+
213
+ * _repos_
214
+
215
+ Required. An array of objects of type `RepoInterface`.
216
+
217
+ #### Example
218
+
219
+ This should typically be called within the function passed as a parameter to the `processTask` function in the data extraction phase.
295
220
 
221
+ ```typescript
296
222
  const repos = [
297
- { itemType: 'issues', normalize: normalizeIssue },
298
- { itemType: 'users', normalize: normalizeUser },
299
- { itemType: 'attachments', normalize: normalizeAttachment },
223
+ {
224
+ itemType: 'tasks',
225
+ normalize: normalizeTask,
226
+ }
300
227
  ];
301
228
 
302
- processTask({
303
- task: async ({ adapter }) => {
304
- adapter.initializeRepos(repos);
305
-
306
- if (adapter.event.payload.event_type === EventType.ExtractionDataStart) {
307
- await adapter.getRepo('issues')?.push(issues);
308
- await adapter.emit(ExtractorEventType.ExtractionDataProgress, { progress: 50 });
309
- } else {
310
- await adapter.getRepo('users')?.push(users);
311
- await adapter.getRepo('attachments')?.push(attachments);
312
- await adapter.emit(ExtractorEventType.ExtractionDataDone, { progress: 100 });
313
- }
314
- },
315
- onTimeout: async ({ adapter }) => {
316
- await adapter.postState();
317
- await adapter.emit(ExtractorEventType.ExtractionDataProgress, { progress: 50 });
318
- },
319
- });
229
+ adapter.initializeRepos(repos);
320
230
  ```
321
231
 
322
- ### 4. Attachments Streaming
232
+ ### `WorkerAdapter.getRepo` method
323
233
 
324
- The ADaaS library handles attachments streaming to improve efficiency and reduce complexity for developers. During the extraction phase, developers need only to provide metadata in a specific format for each attachment, and the library manages the streaming process.
234
+ Finds a Repo from the initialized repos.
325
235
 
326
- The Snap-in should provide attachment metadata following the `NormalizedAttachment` interface:
236
+ ### Usage
327
237
 
328
238
  ```typescript
329
- export interface NormalizedAttachment {
330
- url: string;
331
- id: string;
332
- file_name: string;
333
- author_id: string;
334
- parent_id: string;
335
- }
239
+ adapter.getRepo(itemType);
336
240
  ```
337
241
 
338
- ## Loading phases
242
+ #### Parameters
339
243
 
340
- ### 1. Loading Data
244
+ * _itemType_
245
+
246
+ Required. A __string__ that represents the itemType property for the searched repo.
341
247
 
342
- This phase is defined in `load-data.ts` and is responsible for loading the data to the external system.
248
+ #### Return value
343
249
 
344
- Loading is done by providing an ordered list of itemTypes to load and their respective create and update functions.
250
+ An object of type __Repo__ if the repo is found, otherwise __undefined__.
251
+
252
+ #### Example
253
+
254
+ This should typically be called within the function passed as a parameter to the `processTask` function.
345
255
 
346
256
  ```typescript
347
- processTask({
348
- task: async ({ adapter }) => {
349
- const { reports, processed_files } = await adapter.loadItemTypes({
350
- itemTypesToLoad: [
351
- {
352
- itemType: 'tickets',
353
- create: createTicket,
354
- update: updateTicket,
355
- },
356
- {
357
- itemType: 'conversations',
358
- create: createConversation,
359
- update: updateConversation,
360
- },
361
- ],
362
- });
363
-
364
- await adapter.emit(LoaderEventType.DataLoadingDone, {
365
- reports,
366
- processed_files,
367
- });
368
- },
369
- onTimeout: async ({ adapter }) => {
370
- await adapter.emit(LoaderEventType.DataLoadingProgress, {
371
- reports: adapter.reports,
372
- processed_files: adapter.processedFiles,
373
- });
374
- });
257
+ // Push users to the repository designated for 'users' data.
258
+ await adapter.getRepo('users')?.push(users);
259
+ ```
260
+
261
+ ### `WorkerAdapter.emit` method
262
+
263
+ Emits an event to the Airdrop platform.
264
+
265
+ ### Usage
266
+
267
+ ```typescript
268
+ adapter.emit( newEventType, data ):
375
269
  ```
376
270
 
377
- The loading functions `create` and `update` provide loading to the external system. They provide denormalization of the records to the schema of the external system and provide HTTP calls to the external system. Both loading functions must handle rate limiting for the external system and handle errors.
271
+ #### Parameters
378
272
 
379
- Functions return an ID and modified date of the record in the external system, or specify rate-liming offset or errors, if the record could not be created or updated.
273
+ * _newEventType_
274
+
275
+ Required. The event type to be emitted, of type __ExtractorEventType__ or __LoaderEventType__.
380
276
 
381
- ### 2. Loading Attachments
277
+ * _data_
278
+
279
+ Optional. An object of type __EventData__ which represents the data to be sent with the event.
382
280
 
383
- This phase is defined in `load-attachments.ts` and is responsible for loading the attachments to the external system.
281
+ #### Return value
384
282
 
385
- Loading is done by providing the create function to create attachments in the external system.
283
+ A __promise__, which resolves to undefined after the emit function completes its execution or rejects with an error.
284
+
285
+ #### Example
286
+
287
+ This should typically be called within the function passed as a parameter to the `processTask` function.
386
288
 
387
289
  ```typescript
388
- processTask({
389
- task: async ({ adapter }) => {
390
- const { reports, processed_files } = await adapter.loadAttachments({
391
- create,
392
- });
290
+ // Emitting successfully finished data extraction.
291
+ await adapter.emit(ExtractorEventType.ExtractionDataDone);
393
292
 
394
- await adapter.emit(LoaderEventType.AttachmentLoadingDone, {
395
- reports,
396
- processed_files,
397
- });
398
- },
399
- onTimeout: async ({ adapter }) => {
400
- await adapter.postState();
401
- await adapter.emit(LoaderEventType.AttachmentLoadingProgress, {
402
- reports: adapter.reports,
403
- processed_files: adapter.processedFiles,
404
- });
405
- },
293
+ // Emitting a delay in attachments extraction phase.
294
+ await adapter.emit(ExtractorEventType.ExtractionAttachmentsDelay, {
295
+ delay: 10,
406
296
  });
407
297
  ```
408
-
409
- The loading function `create` provides loading to the external system, to make API calls to the external system to create the attachments and handle errors and external system's rate limiting.
410
-
411
- Functions return an ID and modified date of the record in the external system, specify rate-liming back-off, or log errors, if the attachment could not be created.
@@ -34,7 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.Uploader = void 0;
37
- const axios_devrev_client_1 = require("../../http/axios-devrev-client");
37
+ const axios_client_1 = require("../../http/axios-client");
38
38
  const typescript_sdk_1 = require("@devrev/typescript-sdk");
39
39
  const fs_1 = __importStar(require("fs"));
40
40
  const helpers_1 = require("../common/helpers");
@@ -118,9 +118,9 @@ class Uploader {
118
118
  ) {
119
119
  const formData = (0, helpers_1.createFormData)(preparedArtifact, fetchedObjects);
120
120
  try {
121
- const response = await axios_devrev_client_1.axiosDevRevClient.post(preparedArtifact.url, formData, {
121
+ const response = await axios_client_1.axiosClient.post(preparedArtifact.url, formData, {
122
122
  headers: {
123
- 'Content-Type': 'multipart/form',
123
+ 'Content-Type': 'multipart/form-data',
124
124
  },
125
125
  });
126
126
  return response;
@@ -7,6 +7,7 @@ export declare class Repo {
7
7
  private normalize?;
8
8
  private uploader;
9
9
  private onUpload;
10
+ private options?;
10
11
  constructor({ event, itemType, normalize, onUpload, options, }: RepoFactoryInterface);
11
12
  getItems(): (NormalizedItem | NormalizedAttachment | Item)[];
12
13
  upload(batch?: (NormalizedItem | NormalizedAttachment | Item)[]): Promise<void | ErrorRecord>;
package/dist/repo/repo.js CHANGED
@@ -10,6 +10,7 @@ class Repo {
10
10
  this.normalize = normalize;
11
11
  this.onUpload = onUpload;
12
12
  this.uploader = new uploader_1.Uploader({ event, options });
13
+ this.options = options;
13
14
  }
14
15
  getItems() {
15
16
  return this.items;
@@ -35,6 +36,7 @@ class Repo {
35
36
  }
36
37
  }
37
38
  async push(items) {
39
+ var _a;
38
40
  let recordsToPush;
39
41
  if (!items || items.length === 0) {
40
42
  console.log(`No items to push for type ${this.itemType}. Skipping push.`);
@@ -52,9 +54,10 @@ class Repo {
52
54
  // Add the new records to the items array
53
55
  this.items.push(...recordsToPush);
54
56
  // Upload in batches while the number of items exceeds the batch size
55
- while (this.items.length >= constants_1.ARTIFACT_BATCH_SIZE) {
56
- // Slice out a batch of ARTIFACT_BATCH_SIZE items to upload
57
- const batch = this.items.splice(0, constants_1.ARTIFACT_BATCH_SIZE);
57
+ const batchSize = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.batchSize) || constants_1.ARTIFACT_BATCH_SIZE;
58
+ while (this.items.length >= batchSize) {
59
+ // Slice out a batch of batchSize items to upload
60
+ const batch = this.items.splice(0, batchSize);
58
61
  try {
59
62
  // Upload the batch
60
63
  await this.upload(batch);
@@ -15,7 +15,6 @@ describe('Repo class push method', () => {
15
15
  itemType: 'test_item_type',
16
16
  normalize,
17
17
  onUpload: jest.fn(),
18
- options: {},
19
18
  });
20
19
  });
21
20
  afterEach(() => {
@@ -83,4 +82,36 @@ describe('Repo class push method', () => {
83
82
  expect(uploadSpy).toHaveBeenCalledTimes(2); // Check that upload was called twice
84
83
  uploadSpy.mockRestore();
85
84
  });
85
+ describe('should take batch size into account', () => {
86
+ beforeEach(() => {
87
+ repo = new repo_1.Repo({
88
+ event: (0, test_helpers_1.createEvent)({ eventType: types_1.EventType.ExtractionDataStart }),
89
+ itemType: 'test_item_type',
90
+ normalize,
91
+ onUpload: jest.fn(),
92
+ options: {
93
+ batchSize: 50,
94
+ },
95
+ });
96
+ });
97
+ it('should empty the items array after pushing 50 items with batch size of 50', async () => {
98
+ const items = (0, test_helpers_1.createItems)(50);
99
+ await repo.push(items);
100
+ expect(repo.getItems()).toEqual([]);
101
+ });
102
+ it('should leave 5 items in the items array after pushing 205 items with batch size of 50', async () => {
103
+ const items = (0, test_helpers_1.createItems)(205);
104
+ await repo.push(items);
105
+ expect(repo.getItems().length).toBe(5);
106
+ });
107
+ it('should upload 4 batches of 50 and leave 5 items in the items array after pushing 205 items with batch size of 50', async () => {
108
+ const uploadSpy = jest.spyOn(repo, 'upload');
109
+ const items = (0, test_helpers_1.createItems)(205);
110
+ await repo.push(items);
111
+ expect(normalize).toHaveBeenCalledTimes(205);
112
+ expect(repo.getItems().length).toBe(5);
113
+ expect(uploadSpy).toHaveBeenCalledTimes(4);
114
+ uploadSpy.mockRestore();
115
+ });
116
+ });
86
117
  });
@@ -22,10 +22,12 @@ export interface WorkerAdapterInterface<ConnectorState> {
22
22
  * @constructor
23
23
  * @param {boolean=} isLocalDevelopment - A flag to indicate if the adapter is being used in local development
24
24
  * @param {number=} timeout - The timeout for the worker thread
25
+ * @param {number=} batchSize - Maximum number of extracted items in a batch
25
26
  */
26
27
  export interface WorkerAdapterOptions {
27
28
  isLocalDevelopment?: boolean;
28
29
  timeout?: number;
30
+ batchSize?: number;
29
31
  }
30
32
  /**
31
33
  * SpawnInterface is an interface for Spawn class.
@@ -1,11 +1,11 @@
1
- import { betaSDK } from '@devrev/typescript-sdk';
2
1
  import { NormalizedAttachment } from '../repo/repo.interfaces';
3
- import { UploadResponse, UploaderFactoryInterface } from './uploader.interfaces';
2
+ import { ArtifactsPrepareResponse, UploadResponse, UploaderFactoryInterface } from './uploader.interfaces';
4
3
  import { AxiosResponse } from 'axios';
5
4
  export declare class Uploader {
6
5
  private event;
7
- private betaDevrevSdk;
8
6
  private isLocalDevelopment?;
7
+ private devrevApiEndpoint;
8
+ private devrevApiToken;
9
9
  constructor({ event, options }: UploaderFactoryInterface);
10
10
  /**
11
11
  * Uploads the fetched objects to the DevRev platform.
@@ -17,9 +17,9 @@ export declare class Uploader {
17
17
  * or error information if there was an error
18
18
  */
19
19
  upload(itemType: string, fetchedObjects: object[] | object): Promise<UploadResponse>;
20
- prepareArtifact(filename: string, fileType: string): Promise<betaSDK.ArtifactsPrepareResponse | void>;
20
+ prepareArtifact(filename: string, fileType: string): Promise<ArtifactsPrepareResponse | void>;
21
21
  private uploadToArtifact;
22
- streamToArtifact(preparedArtifact: betaSDK.ArtifactsPrepareResponse, fileStreamResponse: any): Promise<AxiosResponse | void>;
22
+ streamToArtifact(preparedArtifact: ArtifactsPrepareResponse, fileStreamResponse: any): Promise<AxiosResponse | void>;
23
23
  getAttachmentsFromArtifactId({ artifact, }: {
24
24
  artifact: string;
25
25
  }): Promise<{
@@ -16,7 +16,6 @@ export interface Artifact {
16
16
  }
17
17
  /**
18
18
  * ArtifactsPrepareResponse is an interface that defines the structure of the response from the prepare artifacts endpoint.
19
- * @deprecated
20
19
  */
21
20
  export interface ArtifactsPrepareResponse {
22
21
  url: string;
@@ -42,16 +42,13 @@ const axios_client_1 = require("../http/axios-client");
42
42
  const zlib_1 = __importDefault(require("zlib"));
43
43
  const js_jsonl_1 = require("js-jsonl");
44
44
  const form_data_1 = __importDefault(require("form-data"));
45
- const typescript_sdk_1 = require("@devrev/typescript-sdk");
46
45
  const constants_1 = require("../common/constants");
47
46
  const logger_1 = require("../logger/logger");
48
47
  class Uploader {
49
48
  constructor({ event, options }) {
50
49
  this.event = event;
51
- this.betaDevrevSdk = typescript_sdk_1.client.setupBeta({
52
- endpoint: event.execution_metadata.devrev_endpoint,
53
- token: event.context.secrets.service_account_token,
54
- });
50
+ this.devrevApiEndpoint = event.execution_metadata.devrev_endpoint;
51
+ this.devrevApiToken = event.context.secrets.service_account_token;
55
52
  this.isLocalDevelopment = options === null || options === void 0 ? void 0 : options.isLocalDevelopment;
56
53
  }
57
54
  /**
@@ -101,9 +98,13 @@ class Uploader {
101
98
  }
102
99
  async prepareArtifact(filename, fileType) {
103
100
  try {
104
- const response = await this.betaDevrevSdk.artifactsPrepare({
101
+ const response = await axios_client_1.axiosClient.post(`${this.devrevApiEndpoint}/artifacts.prepare`, {
105
102
  file_name: filename,
106
103
  file_type: fileType,
104
+ }, {
105
+ headers: {
106
+ Authorization: `Bearer ${this.devrevApiToken}`,
107
+ },
107
108
  });
108
109
  return response.data;
109
110
  }
@@ -116,9 +117,7 @@ class Uploader {
116
117
  }
117
118
  }
118
119
  }
119
- async uploadToArtifact(preparedArtifact, file
120
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
121
- ) {
120
+ async uploadToArtifact(preparedArtifact, file) {
122
121
  const formData = new form_data_1.default();
123
122
  for (const field of preparedArtifact.form_data) {
124
123
  formData.append(field.key, field.value);
@@ -139,22 +138,23 @@ class Uploader {
139
138
  }
140
139
  }
141
140
  }
142
- async streamToArtifact(preparedArtifact,
143
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
- fileStreamResponse) {
141
+ async streamToArtifact(preparedArtifact, fileStreamResponse) {
145
142
  const formData = new form_data_1.default();
146
143
  for (const field of preparedArtifact.form_data) {
147
144
  formData.append(field.key, field.value);
148
145
  }
149
146
  formData.append('file', fileStreamResponse.data);
150
147
  if (fileStreamResponse.headers['content-length'] > constants_1.MAX_DEVREV_ARTIFACT_SIZE) {
148
+ console.warn(`File size exceeds the maximum limit of ${constants_1.MAX_DEVREV_ARTIFACT_SIZE} bytes.`);
151
149
  return;
152
150
  }
153
151
  try {
154
152
  const response = await axios_client_1.axiosClient.post(preparedArtifact.url, formData, {
155
- headers: Object.assign(Object.assign({}, formData.getHeaders()), (!fileStreamResponse.headers['content-length'] && {
156
- 'Content-Length': constants_1.MAX_DEVREV_ARTIFACT_SIZE,
157
- })),
153
+ headers: Object.assign(Object.assign({}, formData.getHeaders()), (!fileStreamResponse.headers['content-length']
154
+ ? {
155
+ 'Content-Length': constants_1.MAX_DEVREV_ARTIFACT_SIZE,
156
+ }
157
+ : {})),
158
158
  });
159
159
  return response;
160
160
  }
@@ -169,28 +169,28 @@ class Uploader {
169
169
  }
170
170
  }
171
171
  async getAttachmentsFromArtifactId({ artifact, }) {
172
- // 1. Get the URL of the attachments metadata artifact
172
+ // Get the URL of the attachments metadata artifact
173
173
  const artifactUrl = await this.getArtifactDownloadUrl(artifact);
174
174
  if (!artifactUrl) {
175
175
  return {
176
176
  error: { message: 'Error while getting artifact download URL.' },
177
177
  };
178
178
  }
179
- // 2. Download artifact from the URL
179
+ // Download artifact from the URL
180
180
  const gzippedJsonlObject = await this.downloadArtifact(artifactUrl);
181
181
  if (!gzippedJsonlObject) {
182
182
  return {
183
183
  error: { message: 'Error while downloading gzipped jsonl object.' },
184
184
  };
185
185
  }
186
- // 3. Decompress the gzipped jsonl object
186
+ // Decompress the gzipped jsonl object
187
187
  const jsonlObject = this.decompressGzip(gzippedJsonlObject);
188
188
  if (!jsonlObject) {
189
189
  return {
190
190
  error: { message: 'Error while decompressing gzipped jsonl object.' },
191
191
  };
192
192
  }
193
- // 4. Parse the jsonl object to get the attachment metadata
193
+ // Parse the jsonl object to get the attachment metadata
194
194
  const jsonObject = this.parseJsonl(jsonlObject);
195
195
  if (!jsonObject) {
196
196
  return {
@@ -201,8 +201,12 @@ class Uploader {
201
201
  }
202
202
  async getArtifactDownloadUrl(artifactId) {
203
203
  try {
204
- const response = await this.betaDevrevSdk.artifactsLocate({
204
+ const response = await axios_client_1.axiosClient.post(`${this.devrevApiEndpoint}/artifacts.locate`, {
205
205
  id: artifactId,
206
+ }, {
207
+ headers: {
208
+ Authorization: `Bearer ${this.devrevApiToken}`,
209
+ },
206
210
  });
207
211
  return response.data.url;
208
212
  }
@@ -7,6 +7,9 @@ const getAttachmentStream = async ({ item, }) => {
7
7
  try {
8
8
  const fileStreamResponse = await axios_client_1.axiosClient.get(url, {
9
9
  responseType: 'stream',
10
+ headers: {
11
+ 'Accept-Encoding': 'identity',
12
+ },
10
13
  });
11
14
  return { httpStream: fileStreamResponse };
12
15
  }
@@ -51,7 +54,6 @@ const getAttachmentStream = async ({ item, }) => {
51
54
  }
52
55
  },
53
56
  onTimeout: async ({ adapter }) => {
54
- await adapter.postState();
55
57
  await adapter.emit(index_1.ExtractorEventType.ExtractionAttachmentsProgress, {
56
58
  progress: 50,
57
59
  });
@@ -93,7 +93,6 @@ const repos = [
93
93
  }
94
94
  },
95
95
  onTimeout: async ({ adapter }) => {
96
- await adapter.postState();
97
96
  await adapter.emit(index_1.ExtractorEventType.ExtractionDataProgress, {
98
97
  progress: 50,
99
98
  });
@@ -11,7 +11,6 @@ const types_1 = require("../../types");
11
11
  });
12
12
  },
13
13
  onTimeout: async ({ adapter }) => {
14
- await adapter.postState();
15
14
  await adapter.emit(types_1.LoaderEventType.AttachmentLoadingError, {
16
15
  reports: adapter.reports,
17
16
  processed_files: adapter.processedFiles,
@@ -10,7 +10,6 @@ const loading_1 = require("../../types/loading");
10
10
  });
11
11
  },
12
12
  onTimeout: async ({ adapter }) => {
13
- await adapter.postState();
14
13
  await adapter.emit(loading_1.LoaderEventType.DataLoadingError, {
15
14
  reports: adapter.reports,
16
15
  processed_files: adapter.processedFiles,
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WorkerAdapter = void 0;
4
4
  exports.createWorkerAdapter = createWorkerAdapter;
5
- const axios_devrev_client_1 = require("../http/axios-devrev-client");
5
+ const axios_client_1 = require("../http/axios-client");
6
6
  const extraction_1 = require("../types/extraction");
7
7
  const loading_1 = require("../types/loading");
8
8
  const constants_1 = require("../common/constants");
@@ -153,7 +153,7 @@ class WorkerAdapter {
153
153
  node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage(message);
154
154
  }
155
155
  catch (error) {
156
- if (axios_devrev_client_1.axios.isAxiosError(error)) {
156
+ if (axios_client_1.axios.isAxiosError(error)) {
157
157
  console.error(`Error while emitting event with event type: ${newEventType}`, (0, logger_1.serializeAxiosError)(error));
158
158
  }
159
159
  else {
@@ -374,7 +374,7 @@ class WorkerAdapter {
374
374
  });
375
375
  }
376
376
  catch (error) {
377
- if (axios_devrev_client_1.axios.isAxiosError(error)) {
377
+ if (axios_client_1.axios.isAxiosError(error)) {
378
378
  console.warn('Failed to update sync mapper record', (0, logger_1.serializeAxiosError)(error));
379
379
  return {
380
380
  error: {
@@ -419,7 +419,7 @@ class WorkerAdapter {
419
419
  // TODO: Update mapper (optional)
420
420
  }
421
421
  catch (error) {
422
- if (axios_devrev_client_1.axios.isAxiosError(error)) {
422
+ if (axios_client_1.axios.isAxiosError(error)) {
423
423
  if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
424
424
  // Create item in external system if mapper record not found
425
425
  const { id, delay, error } = await itemTypeToLoad.create({
@@ -444,7 +444,7 @@ class WorkerAdapter {
444
444
  };
445
445
  }
446
446
  catch (error) {
447
- if (axios_devrev_client_1.axios.isAxiosError(error)) {
447
+ if (axios_client_1.axios.isAxiosError(error)) {
448
448
  console.warn('Failed to create sync mapper record', (0, logger_1.serializeAxiosError)(error));
449
449
  return {
450
450
  error: {
@@ -511,14 +511,12 @@ class WorkerAdapter {
511
511
  const fileType = ((_a = httpStream.headers) === null || _a === void 0 ? void 0 : _a['content-type']) || 'application/octet-stream';
512
512
  const preparedArtifact = await this.uploader.prepareArtifact(attachment.file_name, fileType);
513
513
  if (!preparedArtifact) {
514
- console.warn('Error while preparing artifact for attachment ID ' +
515
- attachment.id +
516
- '. Skipping attachment');
514
+ console.warn(`Error while preparing artifact for attachment ID ${attachment.id}. Skipping attachment.`);
517
515
  return;
518
516
  }
519
517
  const uploadedArtifact = await this.uploader.streamToArtifact(preparedArtifact, httpStream);
520
518
  if (!uploadedArtifact) {
521
- console.warn('Error while preparing artifact for attachment ID ' + attachment.id);
519
+ console.warn(`Error while streaming to artifact for attachment ID ${attachment.id}. Skipping attachment.`);
522
520
  return;
523
521
  }
524
522
  const ssorAttachment = {
@@ -579,17 +577,24 @@ class WorkerAdapter {
579
577
  * or error information if there was an error
580
578
  */
581
579
  async streamAttachments({ stream, processors, }) {
582
- var _a, _b, _c;
580
+ var _a, _b, _c, _d;
583
581
  const repos = [
584
582
  {
585
583
  itemType: 'ssor_attachment',
586
584
  },
587
585
  ];
588
586
  this.initializeRepos(repos);
589
- const attachmentsState = (((_a = this.state.toDevRev) === null || _a === void 0 ? void 0 : _a.attachmentsMetadata.artifactIds) || []).slice();
590
- console.log('Attachments metadata artifact IDs', attachmentsState);
591
- for (const attachmentsMetadataArtifactId of attachmentsState) {
592
- console.log(`Started processing attachments for artifact ID: ${attachmentsMetadataArtifactId}.`);
587
+ const attachmentsMetadataArtifactIds = (_b = (_a = this.state.toDevRev) === null || _a === void 0 ? void 0 : _a.attachmentsMetadata) === null || _b === void 0 ? void 0 : _b.artifactIds;
588
+ if (!attachmentsMetadataArtifactIds ||
589
+ attachmentsMetadataArtifactIds.length === 0) {
590
+ console.log(`No attachments metadata artifact IDs found in state.`);
591
+ return;
592
+ }
593
+ else {
594
+ console.log(`Found ${attachmentsMetadataArtifactIds.length} attachments metadata artifact IDs in state.`);
595
+ }
596
+ for (const attachmentsMetadataArtifactId of attachmentsMetadataArtifactIds) {
597
+ console.log(`Started processing attachments for attachments metadata artifact ID: ${attachmentsMetadataArtifactId}.`);
593
598
  const { attachments, error } = await this.uploader.getAttachmentsFromArtifactId({
594
599
  artifact: attachmentsMetadataArtifactId,
595
600
  });
@@ -601,6 +606,7 @@ class WorkerAdapter {
601
606
  console.warn(`No attachments found for artifact ID: ${attachmentsMetadataArtifactId}.`);
602
607
  continue;
603
608
  }
609
+ console.log(`Found ${attachments.length} attachments for artifact ID: ${attachmentsMetadataArtifactId}.`);
604
610
  if (processors) {
605
611
  console.log(`Using custom processors for attachments.`);
606
612
  const { reducer, iterator } = processors;
@@ -616,7 +622,7 @@ class WorkerAdapter {
616
622
  }
617
623
  else {
618
624
  console.log(`Using default processors for attachments.`);
619
- const attachmentsToProcess = attachments.slice((_c = (_b = this.state.toDevRev) === null || _b === void 0 ? void 0 : _b.attachmentsMetadata) === null || _c === void 0 ? void 0 : _c.lastProcessed, attachments.length);
625
+ const attachmentsToProcess = attachments.slice((_d = (_c = this.state.toDevRev) === null || _c === void 0 ? void 0 : _c.attachmentsMetadata) === null || _d === void 0 ? void 0 : _d.lastProcessed, attachments.length);
620
626
  for (const attachment of attachmentsToProcess) {
621
627
  const response = await this.processAttachment(attachment, stream);
622
628
  if (response === null || response === void 0 ? void 0 : response.delay) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devrev/ts-adaas",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "DevRev ADaaS (AirDrop-as-a-Service) Typescript SDK.",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",
@@ -1,3 +0,0 @@
1
- import axios from 'axios';
2
- declare const axiosDevRevClient: import("axios").AxiosInstance;
3
- export { axios, axiosDevRevClient };
@@ -1,37 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.axiosDevRevClient = exports.axios = void 0;
7
- const axios_1 = __importDefault(require("axios"));
8
- exports.axios = axios_1.default;
9
- const axios_retry_1 = __importDefault(require("axios-retry"));
10
- const axiosDevRevClient = axios_1.default.create();
11
- exports.axiosDevRevClient = axiosDevRevClient;
12
- (0, axios_retry_1.default)(axiosDevRevClient, {
13
- retries: 5,
14
- retryDelay: (retryCount, error) => {
15
- var _a, _b;
16
- console.warn('Retry attempt: ' + retryCount + ' to url: ' + ((_a = error.config) === null || _a === void 0 ? void 0 : _a.url) + '.');
17
- if (error.response) {
18
- const retry_after = (_b = error.response) === null || _b === void 0 ? void 0 : _b.headers['retry-after'];
19
- if (retry_after) {
20
- return retry_after;
21
- }
22
- }
23
- // Exponential backoff algorithm: 1 * 2 ^ retryCount * 1000ms
24
- return axios_retry_1.default.exponentialDelay(retryCount, error, 1000);
25
- },
26
- retryCondition: (error) => {
27
- var _a;
28
- return (axios_retry_1.default.isNetworkOrIdempotentRequestError(error) ||
29
- ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 429);
30
- },
31
- onMaxRetryTimesExceeded(error, retryCount) {
32
- var _a;
33
- console.log(`Max retries attempted: ${retryCount}`);
34
- (_a = error.config) === null || _a === void 0 ? true : delete _a.headers.Authorization;
35
- delete error.request._header;
36
- },
37
- });