@devrev/ts-adaas 1.2.0-beta → 1.2.1

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
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## Release Notes
4
4
 
5
+ ### v1.2.1
6
+
7
+ - Reduced the `delayFactor` to minimize unnecessary delays.
8
+ - Correct the setting of the `lastSyncStarted` timestamp.
9
+ - Improve logging for attachment extraction and loading.
10
+ - Fix several bugs related to the control protocol.
11
+
5
12
  ### v1.2.0
6
13
 
7
14
  - Add support for loading attachments from DevRev to external system.
@@ -85,6 +92,8 @@
85
92
 
86
93
  # Overview
87
94
 
95
+ [![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)
96
+
88
97
  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.
89
98
 
90
99
  It provides features such as:
@@ -359,7 +368,7 @@ This phase is defined in `load-attachments.ts` and is responsible for loading th
359
368
  Loading is done by providing the create function to create attachments in the external system.
360
369
 
361
370
  ```typescript
362
- processTask({
371
+ processTask({
363
372
  task: async ({ adapter }) => {
364
373
  const { reports, processed_files } = await adapter.loadAttachments({
365
374
  create,
@@ -378,7 +387,6 @@ Loading is done by providing the create function to create attachments in the ex
378
387
  });
379
388
  },
380
389
  });
381
-
382
390
  ```
383
391
 
384
392
  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.
@@ -1,3 +1,4 @@
1
+ import { AxiosResponse } from 'axios';
1
2
  import { AirdropEvent, EventData, ExtractorEventType } from '../types/extraction';
2
3
  import { LoaderEventType } from '../types/loading';
3
4
  export interface EmitInterface {
@@ -5,4 +6,4 @@ export interface EmitInterface {
5
6
  eventType: ExtractorEventType | LoaderEventType;
6
7
  data?: EventData;
7
8
  }
8
- export declare const emit: ({ event, eventType, data, }: EmitInterface) => Promise<void | Error>;
9
+ export declare const emit: ({ event, eventType, data, }: EmitInterface) => Promise<AxiosResponse>;
@@ -2,35 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.emit = void 0;
4
4
  const axios_client_1 = require("../http/axios-client");
5
- const logger_1 = require("../logger/logger");
6
5
  const emit = async ({ event, eventType, data, }) => {
7
6
  const newEvent = {
8
7
  event_type: eventType,
9
8
  event_context: event.payload.event_context,
10
9
  event_data: Object.assign({}, data),
11
10
  };
12
- return new Promise(async (resolve, reject) => {
13
- console.info('Emitting event', JSON.stringify(newEvent));
14
- try {
15
- await axios_client_1.axiosClient.post(event.payload.event_context.callback_url, Object.assign({}, newEvent), {
16
- headers: {
17
- Accept: 'application/json, text/plain, */*',
18
- Authorization: event.context.secrets.service_account_token,
19
- 'Content-Type': 'application/json',
20
- },
21
- });
22
- resolve();
23
- }
24
- catch (error) {
25
- if (axios_client_1.axios.isAxiosError(error)) {
26
- console.error(`Failed to emit event with event type ${eventType}.`, (0, logger_1.serializeAxiosError)(error));
27
- }
28
- else {
29
- // TODO: Stop it through UI or think about retrying this request. Implement exponential retry mechanism.
30
- console.error(`Failed to emit event with event type ${eventType}.`, error);
31
- }
32
- reject();
33
- }
11
+ console.info('Emitting event', JSON.stringify(newEvent));
12
+ return axios_client_1.axiosClient.post(event.payload.event_context.callback_url, Object.assign({}, newEvent), {
13
+ headers: {
14
+ Accept: 'application/json, text/plain, */*',
15
+ Authorization: event.context.secrets.service_account_token,
16
+ 'Content-Type': 'application/json',
17
+ },
34
18
  });
35
19
  };
36
20
  exports.emit = emit;
@@ -14,8 +14,8 @@ exports.axiosClient = axiosClient;
14
14
  retryDelay: (retryCount, error) => {
15
15
  var _a;
16
16
  console.warn('Retry attempt: ' + retryCount + 'to url: ' + ((_a = error.config) === null || _a === void 0 ? void 0 : _a.url) + '.');
17
- // Exponential backoff algorithm: 1 * 2 ^ retryCount * 5000ms
18
- return axios_retry_1.default.exponentialDelay(retryCount, error, 5000);
17
+ // Exponential backoff algorithm: 1 * 2 ^ retryCount * 1000ms
18
+ return axios_retry_1.default.exponentialDelay(retryCount, error, 1000);
19
19
  },
20
20
  retryCondition: (error) => {
21
21
  var _a, _b, _c, _d;
@@ -36,7 +36,7 @@ export interface NormalizedAttachment {
36
36
  file_name: string;
37
37
  author_id: string;
38
38
  parent_id: string;
39
- inline?: boolean;
39
+ grand_parent_id?: number;
40
40
  }
41
41
  /**
42
42
  * Item is an interface that defines the structure of an item.
package/dist/repo/repo.js CHANGED
@@ -36,6 +36,10 @@ class Repo {
36
36
  }
37
37
  async push(items) {
38
38
  let recordsToPush;
39
+ if (!items || items.length === 0) {
40
+ console.log(`No items to push for type ${this.itemType}. Skipping push.`);
41
+ return true;
42
+ }
39
43
  // Normalize items if needed
40
44
  if (this.normalize &&
41
45
  this.itemType != constants_1.AIRDROP_DEFAULT_ITEM_TYPES.EXTERNAL_DOMAIN_METADATA &&
@@ -47,7 +51,6 @@ class Repo {
47
51
  }
48
52
  // Add the new records to the items array
49
53
  this.items.push(...recordsToPush);
50
- console.info(`Extracted ${recordsToPush.length} new items of type ${this.itemType}. Total number of items in repo: ${this.items.length}.`);
51
54
  // Upload in batches while the number of items exceeds the batch size
52
55
  while (this.items.length >= constants_1.ARTIFACT_BATCH_SIZE) {
53
56
  // Slice out a batch of ARTIFACT_BATCH_SIZE items to upload
@@ -16,6 +16,11 @@ async function createAdapterState({ event, initialState, options, }) {
16
16
  });
17
17
  if (!constants_1.STATELESS_EVENT_TYPES.includes(event.payload.event_type)) {
18
18
  await as.fetchState(newInitialState);
19
+ if (event.payload.event_type === extraction_1.EventType.ExtractionDataStart &&
20
+ !as.state.lastSyncStarted) {
21
+ as.state.lastSyncStarted = new Date().toISOString();
22
+ console.log(`Setting lastSyncStarted to ${as.state.lastSyncStarted}.`);
23
+ }
19
24
  }
20
25
  return as;
21
26
  }
@@ -29,7 +34,7 @@ class State {
29
34
  },
30
35
  }
31
36
  : {
32
- lastSyncStarted: new Date().toISOString(),
37
+ lastSyncStarted: '',
33
38
  lastSuccessfulSyncStarted: '',
34
39
  toDevRev: {
35
40
  attachmentsMetadata: {
@@ -107,7 +112,7 @@ class State {
107
112
  if (axios_client_1.axios.isAxiosError(error) && ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
108
113
  const state = Object.assign(Object.assign({}, initialState), this.initialSdkState);
109
114
  this.state = state;
110
- console.log('State not found, returning initial state. Current state:', (0, logger_1.getPrintableState)(this.state));
115
+ console.log('State not found, returning initial state. Current state', (0, logger_1.getPrintableState)(this.state));
111
116
  await this.postState(this.state);
112
117
  return this.state;
113
118
  }
@@ -2,8 +2,9 @@ import { InputData } from '@devrev/typescript-sdk/dist/snap-ins';
2
2
  import { Artifact } from '../uploader/uploader.interfaces';
3
3
  import { ErrorRecord } from './common';
4
4
  import { DonV2, LoaderReport, RateLimited } from './loading';
5
- import { NormalizedAttachment } from 'repo/repo.interfaces';
5
+ import { NormalizedAttachment } from '../repo/repo.interfaces';
6
6
  import { AxiosResponse } from 'axios';
7
+ import { WorkerAdapter } from '../workers/worker-adapter';
7
8
  /**
8
9
  * EventType is an enum that defines the different types of events that can be sent to the external extractor from ADaaS.
9
10
  * The external extractor can use these events to know what to do next in the extraction process.
@@ -164,6 +165,12 @@ export interface EventData {
164
165
  processed_files?: string[];
165
166
  stats_file?: string;
166
167
  }
168
+ /**
169
+ * WorkerMetadata is an interface that defines the structure of the worker metadata that is sent from the external extractor to ADaaS.
170
+ */
171
+ export interface WorkerMetadata {
172
+ adaas_library_version: string;
173
+ }
167
174
  /**
168
175
  * DomainObject is an interface that defines the structure of a domain object that can be extracted.
169
176
  * It must contain a name, a next chunk ID, the pages, the last modified date, whether it is done, and the count.
@@ -213,6 +220,7 @@ export interface ExtractorEvent {
213
220
  event_type: string;
214
221
  event_context: EventContext;
215
222
  event_data?: EventData;
223
+ worker_metadata?: WorkerMetadata;
216
224
  }
217
225
  /**
218
226
  * LoaderEvent
@@ -221,6 +229,7 @@ export interface LoaderEvent {
221
229
  event_type: string;
222
230
  event_context: EventContext;
223
231
  event_data?: EventData;
232
+ worker_metadata?: WorkerMetadata;
224
233
  }
225
234
  export type ExternalSystemAttachmentStreamingFunction = ({ item, event, }: ExternalSystemAttachmentStreamingParams) => Promise<ExternalSystemAttachmentStreamingResponse>;
226
235
  export interface ExternalSystemAttachmentStreamingParams {
@@ -237,3 +246,30 @@ export interface StreamAttachmentsResponse {
237
246
  report?: LoaderReport;
238
247
  rateLimit?: RateLimited;
239
248
  }
249
+ export type ProcessAttachmentReturnType = {
250
+ delay?: number;
251
+ error?: {
252
+ message: string;
253
+ };
254
+ } | undefined;
255
+ export type StreamAttachmentsReturnType = {
256
+ delay?: number;
257
+ error?: ErrorRecord;
258
+ } | undefined;
259
+ export type ExternalSystemAttachmentReducerFunction<Batch, NewBatch, ConnectorState> = ({ attachments, adapter, }: {
260
+ attachments: Batch;
261
+ adapter: WorkerAdapter<ConnectorState>;
262
+ }) => NewBatch;
263
+ export type ExternalProcessAttachmentFunction = ({ attachment, stream, }: {
264
+ attachment: NormalizedAttachment;
265
+ stream: ExternalSystemAttachmentStreamingFunction;
266
+ }) => Promise<ProcessAttachmentReturnType>;
267
+ export type ExternalSystemAttachmentIteratorFunction<NewBatch, ConnectorState> = ({ reducedAttachments, adapter, stream, }: {
268
+ reducedAttachments: NewBatch;
269
+ adapter: WorkerAdapter<ConnectorState>;
270
+ stream: ExternalSystemAttachmentStreamingFunction;
271
+ }) => Promise<ProcessAttachmentReturnType>;
272
+ export interface ExternalSystemAttachmentProcessors<ConnectorState, Batch, NewBatch> {
273
+ reducer: ExternalSystemAttachmentReducerFunction<Batch, NewBatch, ConnectorState>;
274
+ iterator: ExternalSystemAttachmentIteratorFunction<NewBatch, ConnectorState>;
275
+ }
@@ -1,5 +1,5 @@
1
1
  export { ErrorLevel, ErrorRecord, LogRecord, AdapterUpdateParams, InitialDomainMapping, } from './common';
2
- export { EventType, ExtractorEventType, ExtractionMode, ExternalSyncUnit, EventContextIn, EventContextOut, ConnectionData, EventData, DomainObjectState, AirdropEvent, AirdropMessage, ExtractorEvent, SyncMode, ExternalSystemAttachmentStreamingParams, ExternalSystemAttachmentStreamingResponse, ExternalSystemAttachmentStreamingFunction, } from './extraction';
2
+ export { EventType, ExtractorEventType, ExtractionMode, ExternalSyncUnit, EventContextIn, EventContextOut, ConnectionData, EventData, DomainObjectState, AirdropEvent, AirdropMessage, ExtractorEvent, SyncMode, ExternalSystemAttachmentStreamingParams, ExternalSystemAttachmentStreamingResponse, ExternalSystemAttachmentStreamingFunction, ExternalProcessAttachmentFunction, ExternalSystemAttachmentReducerFunction, ExternalSystemAttachmentIteratorFunction, } from './extraction';
3
3
  export { LoaderEventType, ExternalSystemItem, ExternalSystemItemLoadingResponse, ExternalSystemItemLoadingParams, ExternalSystemAttachment, } from './loading';
4
4
  export { NormalizedItem, NormalizedAttachment, RepoInterface, } from '../repo/repo.interfaces';
5
5
  export { AdapterState } from '../state/state.interfaces';
@@ -28,6 +28,8 @@ export interface ExternalSystemAttachment {
28
28
  created_date: string;
29
29
  modified_by_id: string;
30
30
  modified_date: string;
31
+ parent_id?: string;
32
+ grand_parent_id?: string;
31
33
  }
32
34
  export interface ExternalSystemItem {
33
35
  id: {
@@ -30,7 +30,6 @@ export interface WorkerAdapterOptions {
30
30
  isLocalDevelopment?: boolean;
31
31
  timeout?: number;
32
32
  }
33
- export type SpawnResolve = (value: boolean | PromiseLike<boolean>) => void;
34
33
  /**
35
34
  * SpawnInterface is an interface for Spawn class.
36
35
  * @interface SpawnInterface
@@ -42,7 +41,7 @@ export interface SpawnInterface {
42
41
  event: AirdropEvent;
43
42
  worker: Worker;
44
43
  options?: WorkerAdapterOptions;
45
- resolve: SpawnResolve;
44
+ resolve: (value: void | PromiseLike<void>) => void;
46
45
  }
47
46
  /**
48
47
  * SpawnFactoryInterface is an interface for Spawn class factory.
@@ -96,7 +96,7 @@ class Uploader {
96
96
  item_type: itemType,
97
97
  item_count: Array.isArray(fetchedObjects) ? fetchedObjects.length : 1,
98
98
  };
99
- console.log('Successful upload of artifact: ', artifact);
99
+ console.log('Successful upload of artifact', artifact);
100
100
  return { artifact };
101
101
  }
102
102
  async prepareArtifact(filename, fileType) {
@@ -12,10 +12,12 @@ const getAttachmentStream = async ({ item, }) => {
12
12
  }
13
13
  catch (error) {
14
14
  if (axios_client_1.axios.isAxiosError(error)) {
15
- console.error('Error while fetching attachment from URL.', (0, index_1.serializeAxiosError)(error));
15
+ console.warn(`Error while fetching attachment ${id} from URL.`, (0, index_1.serializeAxiosError)(error));
16
+ console.warn('Failed attachment metadata', item);
16
17
  }
17
18
  else {
18
- console.error('Error while fetching attachment from URL.', error);
19
+ console.warn(`Error while fetching attachment ${id} from URL.`, error);
20
+ console.warn('Failed attachment metadata', item);
19
21
  }
20
22
  return {
21
23
  error: {
@@ -26,20 +28,27 @@ const getAttachmentStream = async ({ item, }) => {
26
28
  };
27
29
  (0, index_1.processTask)({
28
30
  task: async ({ adapter }) => {
29
- const { error, delay } = await adapter.streamAttachments({
30
- stream: getAttachmentStream,
31
- });
32
- if (delay) {
33
- await adapter.emit(index_1.ExtractorEventType.ExtractionAttachmentsDelay, {
34
- delay,
31
+ try {
32
+ const response = await adapter.streamAttachments({
33
+ stream: getAttachmentStream,
35
34
  });
35
+ if (response === null || response === void 0 ? void 0 : response.delay) {
36
+ await adapter.emit(index_1.ExtractorEventType.ExtractionAttachmentsDelay, {
37
+ delay: response.delay,
38
+ });
39
+ }
40
+ else if (response === null || response === void 0 ? void 0 : response.error) {
41
+ await adapter.emit(index_1.ExtractorEventType.ExtractionAttachmentsError, {
42
+ error: response.error,
43
+ });
44
+ }
45
+ else {
46
+ await adapter.emit(index_1.ExtractorEventType.ExtractionAttachmentsDone);
47
+ }
36
48
  }
37
- else if (error) {
38
- await adapter.emit(index_1.ExtractorEventType.ExtractionAttachmentsError, {
39
- error,
40
- });
49
+ catch (error) {
50
+ console.error('An error occured while processing a task.', error);
41
51
  }
42
- await adapter.emit(index_1.ExtractorEventType.ExtractionAttachmentsDone);
43
52
  },
44
53
  onTimeout: async ({ adapter }) => {
45
54
  await adapter.postState();
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const process_task_1 = require("workers/process-task");
4
- const types_1 = require("types");
3
+ const process_task_1 = require("../process-task");
4
+ const types_1 = require("../../types");
5
5
  (0, process_task_1.processTask)({
6
6
  task: async ({ adapter }) => {
7
- await adapter.emit(types_1.LoaderEventType.AttachmentLoadingDone, {
8
- reports: adapter.reports,
9
- processed_files: adapter.processedFiles,
7
+ await adapter.emit(types_1.LoaderEventType.UnknownEventType, {
8
+ error: {
9
+ message: 'Event type ' + adapter.event.payload.event_type + ' not supported.'
10
+ }
10
11
  });
11
12
  },
12
13
  onTimeout: async ({ adapter }) => {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const process_task_1 = require("workers/process-task");
3
+ const process_task_1 = require("../process-task");
4
4
  const loading_1 = require("../../types/loading");
5
5
  (0, process_task_1.processTask)({
6
6
  task: async ({ adapter }) => {
@@ -10,16 +10,15 @@ import { SpawnFactoryInterface, SpawnInterface } from '../types/workers';
10
10
  * @param {string} workerPath - The path to the worker file
11
11
  * @returns {Promise<Spawn>} - A new instance of Spawn class
12
12
  */
13
- export declare function spawn<ConnectorState>({ event, initialState, workerPath, options, }: SpawnFactoryInterface<ConnectorState>): Promise<boolean | PromiseLike<boolean>>;
13
+ export declare function spawn<ConnectorState>({ event, initialState, workerPath, options, }: SpawnFactoryInterface<ConnectorState>): Promise<void>;
14
14
  export declare class Spawn {
15
15
  private event;
16
- private hasWorkerEmitted;
16
+ private alreadyEmitted;
17
17
  private defaultLambdaTimeout;
18
18
  private lambdaTimeout;
19
- private worker;
20
- private resolve;
21
19
  private timer;
22
20
  private logger;
21
+ private resolve;
23
22
  constructor({ event, worker, options, resolve }: SpawnInterface);
24
23
  private exitFromMainThread;
25
24
  }
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.Spawn = void 0;
4
7
  exports.spawn = spawn;
8
+ const axios_1 = __importDefault(require("axios"));
5
9
  const extraction_1 = require("../types/extraction");
6
10
  const control_protocol_1 = require("../common/control-protocol");
7
11
  const helpers_1 = require("../common/helpers");
@@ -90,47 +94,58 @@ async function spawn({ event, initialState, workerPath, options, }) {
90
94
  });
91
95
  }
92
96
  catch (error) {
93
- logger.error('Worker error while processing task.', error);
94
- return false;
97
+ logger.error('Worker error while processing task', error);
95
98
  }
96
99
  }
97
100
  else {
98
- await (0, control_protocol_1.emit)({
99
- event,
100
- eventType: extraction_1.ExtractorEventType.UnknownEventType,
101
- data: {
102
- error: {
103
- message: 'Unrecognized event type in spawn ' +
104
- event.payload.event_type +
105
- '.',
101
+ console.error('Script was not found for event type: ' + event.payload.event_type + '.');
102
+ try {
103
+ await (0, control_protocol_1.emit)({
104
+ event,
105
+ eventType: extraction_1.ExtractorEventType.UnknownEventType,
106
+ data: {
107
+ error: {
108
+ message: 'Unrecognized event type in spawn ' +
109
+ event.payload.event_type +
110
+ '.',
111
+ },
106
112
  },
107
- },
108
- });
109
- return false;
113
+ });
114
+ }
115
+ catch (error) {
116
+ if (axios_1.default.isAxiosError(error)) {
117
+ console.error('Error while emitting event', (0, logger_1.serializeAxiosError)(error));
118
+ }
119
+ else {
120
+ console.error('Error while emitting event', error);
121
+ }
122
+ }
110
123
  }
111
124
  }
112
125
  class Spawn {
113
126
  constructor({ event, worker, options, resolve }) {
114
127
  this.defaultLambdaTimeout = 10 * 60 * 1000; // 10 minutes in milliseconds
115
- this.hasWorkerEmitted = false;
128
+ this.alreadyEmitted = false;
116
129
  this.event = event;
130
+ this.logger = new logger_1.Logger({ event, options });
117
131
  this.lambdaTimeout = (options === null || options === void 0 ? void 0 : options.timeout)
118
132
  ? Math.min(options.timeout, this.defaultLambdaTimeout)
119
133
  : this.defaultLambdaTimeout;
120
134
  this.resolve = resolve;
135
+ // if lambda timeout is reached, then send a message to the worker to gracefully exit
121
136
  this.timer = setTimeout(async () => {
122
- this.logger.log('Lambda timeout reached. Exiting.');
123
- if (this.worker) {
124
- this.worker.postMessage({
137
+ this.logger.log('Lambda timeout reached. Sending a message to the worker to gracefully exit.');
138
+ if (worker) {
139
+ worker.postMessage({
125
140
  subject: workers_1.WorkerMessageSubject.WorkerMessageExit,
126
141
  });
127
142
  }
128
143
  else {
144
+ console.log("Worker doesn't exist. Exiting from main thread.");
129
145
  await this.exitFromMainThread();
130
146
  }
131
147
  }, this.lambdaTimeout);
132
- this.logger = new logger_1.Logger({ event, options });
133
- this.worker = worker;
148
+ // if worker exits with process.exit(code) then we need to clear the timer and exit from main thread
134
149
  worker.on(workers_1.WorkerEvent.WorkerExit, async (code) => {
135
150
  this.logger.info('Worker exited with exit code: ' + code + '.');
136
151
  if (this.timer) {
@@ -139,45 +154,57 @@ class Spawn {
139
154
  await this.exitFromMainThread();
140
155
  });
141
156
  worker.on(workers_1.WorkerEvent.WorkerMessage, async (message) => {
142
- if ((message === null || message === void 0 ? void 0 : message.subject) === workers_1.WorkerMessageSubject.WorkerMessageEmitted) {
143
- this.logger.info('Worker has emitted message to ADaaS.');
144
- this.hasWorkerEmitted = true;
145
- }
146
- if ((message === null || message === void 0 ? void 0 : message.subject) === workers_1.WorkerMessageSubject.WorkerMessageDone) {
147
- this.logger.info('Worker has completed work.');
148
- clearTimeout(this.timer);
149
- await this.exitFromMainThread();
150
- }
151
- });
152
- worker.on(workers_1.WorkerEvent.WorkerMessage, (message) => {
153
157
  var _a, _b;
158
+ // if worker send a log message, then log it from the main thread with logger
154
159
  if ((message === null || message === void 0 ? void 0 : message.subject) === workers_1.WorkerMessageSubject.WorkerMessageLog) {
155
160
  const args = (_a = message.payload) === null || _a === void 0 ? void 0 : _a.args;
156
161
  const level = (_b = message.payload) === null || _b === void 0 ? void 0 : _b.level;
157
162
  this.logger.logFn(args, level);
158
163
  }
164
+ // if worker sends a message that it has completed work, then clear the timer and exit from main thread
165
+ if ((message === null || message === void 0 ? void 0 : message.subject) === workers_1.WorkerMessageSubject.WorkerMessageDone) {
166
+ this.logger.info('Worker has completed with executing the task.');
167
+ if (this.timer) {
168
+ clearTimeout(this.timer);
169
+ }
170
+ await this.exitFromMainThread();
171
+ }
172
+ // if worker sends a message that it has emitted an event, then set alreadyEmitted to true
173
+ if ((message === null || message === void 0 ? void 0 : message.subject) === workers_1.WorkerMessageSubject.WorkerMessageEmitted) {
174
+ this.logger.info('Worker has emitted message to ADaaS.');
175
+ this.alreadyEmitted = true;
176
+ }
159
177
  });
160
178
  }
161
179
  async exitFromMainThread() {
162
- if (this.hasWorkerEmitted) {
163
- this.resolve(true);
180
+ if (this.alreadyEmitted) {
181
+ this.resolve();
164
182
  return;
165
183
  }
184
+ this.alreadyEmitted = true;
166
185
  const timeoutEventType = (0, helpers_1.getTimeoutErrorEventType)(this.event.payload.event_type);
167
- if (timeoutEventType !== null) {
186
+ if (timeoutEventType) {
168
187
  const { eventType } = timeoutEventType;
169
- await (0, control_protocol_1.emit)({
170
- eventType,
171
- event: this.event,
172
- data: {
173
- error: {
174
- message: 'Worker has not emitted anything. Exited.',
188
+ try {
189
+ await (0, control_protocol_1.emit)({
190
+ eventType,
191
+ event: this.event,
192
+ data: {
193
+ error: {
194
+ message: 'Worker has not emitted anything. Exited.',
195
+ },
175
196
  },
176
- },
177
- }).then(() => {
178
- this.logger.error('Worker has not emitted anything. Exited.');
179
- this.resolve(true);
180
- });
197
+ });
198
+ this.resolve();
199
+ }
200
+ catch (error) {
201
+ if (axios_1.default.isAxiosError(error)) {
202
+ console.error('Error while emitting event', (0, logger_1.serializeAxiosError)(error));
203
+ }
204
+ else {
205
+ console.error('Error while emitting event', error);
206
+ }
207
+ }
181
208
  }
182
209
  }
183
210
  }
@@ -1,10 +1,10 @@
1
- import { AirdropEvent, ExtractorEventType, EventData, ExternalSystemAttachmentStreamingFunction } from '../types/extraction';
1
+ import { AirdropEvent, ExtractorEventType, EventData, ExternalSystemAttachmentStreamingFunction, ExternalSystemAttachmentProcessors, ProcessAttachmentReturnType, StreamAttachmentsReturnType } from '../types/extraction';
2
2
  import { ExternalSystemAttachment, ExternalSystemLoadingFunction, FileToLoad, LoaderEventType } from '../types/loading';
3
3
  import { AdapterState } from '../state/state.interfaces';
4
4
  import { Artifact } from '../uploader/uploader.interfaces';
5
5
  import { WorkerAdapterInterface, WorkerAdapterOptions } from '../types/workers';
6
6
  import { Repo } from '../repo/repo';
7
- import { RepoInterface } from '../repo/repo.interfaces';
7
+ import { NormalizedAttachment, RepoInterface } from '../repo/repo.interfaces';
8
8
  import { ExternalSystemItem, ItemTypesToLoadParams, ItemTypeToLoad, LoaderReport, LoadItemResponse, LoadItemTypesResponse } from '../types/loading';
9
9
  export declare function createWorkerAdapter<ConnectorState>({ event, adapterState, parentPort, options, }: WorkerAdapterInterface<ConnectorState>): WorkerAdapter<ConnectorState>;
10
10
  /**
@@ -64,6 +64,7 @@ export declare class WorkerAdapter<ConnectorState> {
64
64
  item: ExternalSystemItem;
65
65
  itemTypeToLoad: ItemTypeToLoad;
66
66
  }): Promise<LoadItemResponse>;
67
+ processAttachment: (attachment: NormalizedAttachment, stream: ExternalSystemAttachmentStreamingFunction) => Promise<ProcessAttachmentReturnType>;
67
68
  loadAttachment({ item, create, }: {
68
69
  item: ExternalSystemAttachment;
69
70
  create: ExternalSystemLoadingFunction<ExternalSystemAttachment>;
@@ -71,25 +72,12 @@ export declare class WorkerAdapter<ConnectorState> {
71
72
  /**
72
73
  * Streams the attachments to the DevRev platform.
73
74
  * The attachments are streamed to the platform and the artifact information is returned.
74
- * @param {string} attachmentsMetadataArtifactId - The artifact ID of the attachments metadata
75
- * @returns {Promise<UploadResponse>} - The response object containing the ssoAttachment artifact information
75
+ * @param {{ stream, processors }: { stream: ExternalSystemAttachmentStreamingFunction, processors?: ExternalSystemAttachmentProcessors }} Params - The parameters to stream the attachments
76
+ * @returns {Promise<StreamAttachmentsReturnType>} - The response object containing the ssoAttachment artifact information
76
77
  * or error information if there was an error
77
78
  */
78
- streamAttachments({ stream, }: {
79
+ streamAttachments<NewBatch>({ stream, processors, }: {
79
80
  stream: ExternalSystemAttachmentStreamingFunction;
80
- }): Promise<{
81
- report: {};
82
- error?: undefined;
83
- delay?: undefined;
84
- } | {
85
- error: {
86
- message: string;
87
- };
88
- report?: undefined;
89
- delay?: undefined;
90
- } | {
91
- delay: number;
92
- report?: undefined;
93
- error?: undefined;
94
- }>;
81
+ processors?: ExternalSystemAttachmentProcessors<ConnectorState, NormalizedAttachment[], NewBatch>;
82
+ }): Promise<StreamAttachmentsReturnType>;
95
83
  }
@@ -39,6 +39,49 @@ function createWorkerAdapter({ event, adapterState, parentPort, options, }) {
39
39
  class WorkerAdapter {
40
40
  constructor({ event, adapterState, parentPort, options, }) {
41
41
  this.repos = [];
42
+ this.processAttachment = async (attachment, stream) => {
43
+ var _a, _b;
44
+ const { httpStream, delay, error } = await stream({
45
+ item: attachment,
46
+ event: this.event,
47
+ });
48
+ if (error) {
49
+ console.warn('Error while streaming attachment', error === null || error === void 0 ? void 0 : error.message);
50
+ return { error };
51
+ }
52
+ else if (delay) {
53
+ return { delay };
54
+ }
55
+ if (httpStream) {
56
+ const fileType = ((_a = httpStream.headers) === null || _a === void 0 ? void 0 : _a['content-type']) || 'application/octet-stream';
57
+ const preparedArtifact = await this.uploader.prepareArtifact(attachment.file_name, fileType);
58
+ if (!preparedArtifact) {
59
+ console.warn('Error while preparing artifact for attachment ID ' +
60
+ attachment.id +
61
+ '. Skipping attachment');
62
+ return;
63
+ }
64
+ const uploadedArtifact = await this.uploader.streamToArtifact(preparedArtifact, httpStream);
65
+ if (!uploadedArtifact) {
66
+ console.warn('Error while preparing artifact for attachment ID ' + attachment.id);
67
+ return;
68
+ }
69
+ const ssorAttachment = {
70
+ id: {
71
+ devrev: preparedArtifact.id,
72
+ external: attachment.id,
73
+ },
74
+ parent_id: {
75
+ external: attachment.parent_id,
76
+ },
77
+ actor_id: {
78
+ external: attachment.author_id,
79
+ },
80
+ };
81
+ await ((_b = this.getRepo('ssor_attachment')) === null || _b === void 0 ? void 0 : _b.push([ssorAttachment]));
82
+ }
83
+ return;
84
+ };
42
85
  this.event = event;
43
86
  this.options = options;
44
87
  this.adapterState = adapterState;
@@ -125,6 +168,7 @@ class WorkerAdapter {
125
168
  // If the extraction is done, we want to save the timestamp of the last successful sync
126
169
  if (newEventType === extraction_1.ExtractorEventType.ExtractionAttachmentsDone) {
127
170
  this.state.lastSuccessfulSyncStarted = this.state.lastSyncStarted;
171
+ this.state.lastSyncStarted = '';
128
172
  }
129
173
  // We want to save the state every time we emit an event, except for the start and delete events
130
174
  if (!constants_1.STATELESS_EVENT_TYPES.includes(this.event.payload.event_type)) {
@@ -147,9 +191,15 @@ class WorkerAdapter {
147
191
  };
148
192
  this.artifacts = [];
149
193
  this.parentPort.postMessage(message);
194
+ this.hasWorkerEmitted = true;
150
195
  }
151
196
  catch (error) {
152
- console.error('Error while emitting event with event type: ' + newEventType + '.', error);
197
+ if (axios_devrev_client_1.axios.isAxiosError(error)) {
198
+ console.error('Error while emitting event with event type: ' + newEventType + '.', (0, logger_1.serializeAxiosError)(error));
199
+ }
200
+ else {
201
+ console.error('Error while emitting event with event type: ' + newEventType + '.', error);
202
+ }
153
203
  this.parentPort.postMessage(workers_1.WorkerMessageSubject.WorkerMessageExit);
154
204
  }
155
205
  }
@@ -518,90 +568,66 @@ class WorkerAdapter {
518
568
  /**
519
569
  * Streams the attachments to the DevRev platform.
520
570
  * The attachments are streamed to the platform and the artifact information is returned.
521
- * @param {string} attachmentsMetadataArtifactId - The artifact ID of the attachments metadata
522
- * @returns {Promise<UploadResponse>} - The response object containing the ssoAttachment artifact information
571
+ * @param {{ stream, processors }: { stream: ExternalSystemAttachmentStreamingFunction, processors?: ExternalSystemAttachmentProcessors }} Params - The parameters to stream the attachments
572
+ * @returns {Promise<StreamAttachmentsReturnType>} - The response object containing the ssoAttachment artifact information
523
573
  * or error information if there was an error
524
574
  */
525
- async streamAttachments({ stream, }) {
526
- var _a, _b, _c, _d, _e, _f;
575
+ async streamAttachments({ stream, processors, }) {
576
+ var _a, _b, _c;
527
577
  const repos = [
528
578
  {
529
579
  itemType: 'ssor_attachment',
530
580
  },
531
581
  ];
532
582
  this.initializeRepos(repos);
533
- for (const attachmentsMetadataArtifactId of ((_a = this.state.toDevRev) === null || _a === void 0 ? void 0 : _a.attachmentsMetadata.artifactIds) || []) {
534
- if (((_b = this.state.toDevRev) === null || _b === void 0 ? void 0 : _b.attachmentsMetadata.artifactIds.length) === 0) {
535
- return { report: {} };
536
- }
537
- console.log('Started streaming attachments to the platform.');
583
+ const attachmentsState = (((_a = this.state.toDevRev) === null || _a === void 0 ? void 0 : _a.attachmentsMetadata.artifactIds) || []).slice();
584
+ console.log('Attachments metadata artifact IDs', attachmentsState);
585
+ for (const attachmentsMetadataArtifactId of attachmentsState) {
586
+ console.log(`Started processing attachments for artifact ID: ${attachmentsMetadataArtifactId}.`);
538
587
  const { attachments, error } = await this.uploader.getAttachmentsFromArtifactId({
539
588
  artifact: attachmentsMetadataArtifactId,
540
589
  });
541
590
  if (error) {
591
+ console.error(`Failed to get attachments for artifact ID: ${attachmentsMetadataArtifactId}.`);
542
592
  return { error };
543
593
  }
544
- if (attachments) {
545
- 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);
594
+ if (!attachments || attachments.length === 0) {
595
+ console.warn(`No attachments found for artifact ID: ${attachmentsMetadataArtifactId}.`);
596
+ continue;
597
+ }
598
+ if (processors) {
599
+ console.log(`Using custom processors for attachments.`);
600
+ const { reducer, iterator } = processors;
601
+ const reducedAttachments = reducer({ attachments, adapter: this });
602
+ const response = await iterator({
603
+ reducedAttachments,
604
+ adapter: this,
605
+ stream,
606
+ });
607
+ if ((response === null || response === void 0 ? void 0 : response.delay) || (response === null || response === void 0 ? void 0 : response.error)) {
608
+ return response;
609
+ }
610
+ }
611
+ else {
612
+ console.log(`Using default processors for attachments.`);
613
+ 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);
546
614
  for (const attachment of attachmentsToProcess) {
547
- const { httpStream, delay, error } = await stream({
548
- item: attachment,
549
- event: this.event,
550
- });
551
- if (error) {
552
- console.warn('Error while streaming attachment', error === null || error === void 0 ? void 0 : error.message);
553
- continue;
554
- }
555
- else if (delay) {
556
- return { delay };
615
+ const response = await this.processAttachment(attachment, stream);
616
+ if ((response === null || response === void 0 ? void 0 : response.delay) || (response === null || response === void 0 ? void 0 : response.error)) {
617
+ return response;
557
618
  }
558
- if (httpStream) {
559
- const fileType = ((_e = httpStream.headers) === null || _e === void 0 ? void 0 : _e['content-type']) ||
560
- 'application/octet-stream';
561
- const preparedArtifact = await this.uploader.prepareArtifact(attachment.file_name, fileType);
562
- if (!preparedArtifact) {
563
- console.warn('Error while preparing artifact for attachment ID ' +
564
- attachment.id +
565
- '. Skipping attachment');
566
- if (this.state.toDevRev) {
567
- this.state.toDevRev.attachmentsMetadata.lastProcessed++;
568
- }
569
- continue;
570
- }
571
- const uploadedArtifact = await this.uploader.streamToArtifact(preparedArtifact, httpStream);
572
- if (!uploadedArtifact) {
573
- console.warn('Error while preparing artifact for attachment ID ' +
574
- attachment.id);
575
- if (this.state.toDevRev) {
576
- this.state.toDevRev.attachmentsMetadata.lastProcessed++;
577
- }
578
- continue;
579
- }
580
- const ssorAttachment = {
581
- id: {
582
- devrev: preparedArtifact.id,
583
- external: attachment.id,
584
- },
585
- parent_id: {
586
- external: attachment.parent_id,
587
- },
588
- actor_id: {
589
- external: attachment.author_id,
590
- },
591
- };
592
- await ((_f = this.getRepo('ssor_attachment')) === null || _f === void 0 ? void 0 : _f.push([ssorAttachment]));
593
- if (this.state.toDevRev) {
594
- this.state.toDevRev.attachmentsMetadata.lastProcessed++;
595
- }
619
+ if (this.state.toDevRev) {
620
+ this.state.toDevRev.attachmentsMetadata.lastProcessed += 1;
596
621
  }
597
622
  }
598
623
  }
599
624
  if (this.state.toDevRev) {
625
+ console.log(`Finished processing attachments for artifact ID. Setting last processed to 0 and removing artifact ID from state.`);
600
626
  this.state.toDevRev.attachmentsMetadata.artifactIds.shift();
601
627
  this.state.toDevRev.attachmentsMetadata.lastProcessed = 0;
602
628
  }
603
629
  }
604
- return { report: {} };
630
+ return;
605
631
  }
606
632
  }
607
633
  exports.WorkerAdapter = WorkerAdapter;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devrev/ts-adaas",
3
- "version": "1.2.0-beta",
3
+ "version": "1.2.1",
4
4
  "description": "DevRev ADaaS (AirDrop-as-a-Service) Typescript SDK.",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- export {};
@@ -1,19 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const process_task_1 = require("workers/process-task");
4
- const loading_1 = require("../../types/loading");
5
- (0, process_task_1.processTask)({
6
- task: async ({ adapter }) => {
7
- await adapter.emit(loading_1.LoaderEventType.DataLoadingDone, {
8
- reports: adapter.reports,
9
- processed_files: adapter.processedFiles,
10
- });
11
- },
12
- onTimeout: async ({ adapter }) => {
13
- await adapter.postState();
14
- await adapter.emit(loading_1.LoaderEventType.DataLoadingProgress, {
15
- reports: adapter.reports,
16
- processed_files: adapter.processedFiles,
17
- });
18
- },
19
- });
@@ -1,15 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const index_1 = require("../../index");
4
- (0, index_1.processTask)({
5
- task: async ({ adapter }) => {
6
- await adapter.emit(index_1.LoaderEventType.LoaderStateDeletionDone);
7
- },
8
- onTimeout: async ({ adapter }) => {
9
- await adapter.emit(index_1.LoaderEventType.LoaderStateDeletionError, {
10
- error: {
11
- message: 'Failed to delete data. Lambda timeout.',
12
- },
13
- });
14
- },
15
- });