@devrev/ts-adaas 1.2.6 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,6 +39,104 @@ function createWorkerAdapter({ event, adapterState, options, }) {
39
39
  class WorkerAdapter {
40
40
  constructor({ event, adapterState, options, }) {
41
41
  this.repos = [];
42
+ /**
43
+ * Transforms an array of attachments into array of batches of the specified size.
44
+ *
45
+ * @param {Object} parameters - The parameters object
46
+ * @param {NormalizedAttachment[]} parameters.attachments - Array of attachments to be processed
47
+ * @param {number} [parameters.batchSize=1] - The size of each batch (defaults to 1)
48
+ * @param {ConnectorState} parameters.adapter - The adapter instance
49
+ * @returns {NormalizedAttachment[][]} An array of attachment batches
50
+ */
51
+ this.defaultAttachmentsReducer = ({ attachments, batchSize = 1 }) => {
52
+ // Transform the attachments array into smaller batches
53
+ const batches = attachments.reduce((result, item, index) => {
54
+ // Determine the index of the current batch
55
+ const batchIndex = Math.floor(index / batchSize);
56
+ // Initialize a new batch if it doesn't already exist
57
+ if (!result[batchIndex]) {
58
+ result[batchIndex] = [];
59
+ }
60
+ // Append the current item to the current batch
61
+ result[batchIndex].push(item);
62
+ return result;
63
+ }, []);
64
+ // Return the array of batches
65
+ return batches;
66
+ };
67
+ /**
68
+ * This iterator function processes attachments batch by batch, saves progress to state, and handles rate limiting.
69
+ *
70
+ * @param {Object} parameters - The parameters object
71
+ * @param {NormalizedAttachment[][]} parameters.reducedAttachments - Array of attachment batches to process
72
+ * @param {Object} parameters.adapter - The connector adapter that contains state and processing methods
73
+ * @param {Object} parameters.stream - Stream object for logging or progress reporting
74
+ * @returns {Promise<{delay?: number} | void>} Returns an object with delay information if rate-limited, otherwise void
75
+ * @throws Will not throw exceptions but will log warnings for processing failures
76
+ */
77
+ this.defaultAttachmentsIterator = async ({ reducedAttachments, adapter, stream }) => {
78
+ if (!adapter.state.toDevRev) {
79
+ const error = new Error(`toDevRev state is not defined.`);
80
+ console.error(error.message);
81
+ return { error };
82
+ }
83
+ // Get index of the last processed batch of this artifact
84
+ const lastProcessedBatchIndex = adapter.state.toDevRev.attachmentsMetadata.lastProcessed || 0;
85
+ // Get the list of successfully processed attachments in previous (possibly incomplete) batch extraction.
86
+ // If no such list exists, create an empty one.
87
+ if (!adapter.state.toDevRev.attachmentsMetadata
88
+ .lastProcessedAttachmentsIdsList) {
89
+ adapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList =
90
+ [];
91
+ }
92
+ // Loop through the batches of attachments
93
+ for (let i = lastProcessedBatchIndex; i < reducedAttachments.length; i++) {
94
+ const attachmentsBatch = reducedAttachments[i];
95
+ // Create a list of promises for parallel processing
96
+ const promises = [];
97
+ for (const attachment of attachmentsBatch) {
98
+ if (adapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList.includes(attachment.id)) {
99
+ console.log(`Attachment with ID ${attachment.id} has already been processed. Skipping.`);
100
+ continue; // Skip if the attachment ID is already processed
101
+ }
102
+ const promise = adapter
103
+ .processAttachment(attachment, stream)
104
+ .then((response) => {
105
+ var _a, _b, _c;
106
+ // Check if rate limit was hit
107
+ if (response === null || response === void 0 ? void 0 : response.delay) {
108
+ // Store this promise result to be checked later
109
+ return { delay: response.delay };
110
+ }
111
+ // No rate limiting, process normally
112
+ if ((_b = (_a = adapter.state.toDevRev) === null || _a === void 0 ? void 0 : _a.attachmentsMetadata) === null || _b === void 0 ? void 0 : _b.lastProcessedAttachmentsIdsList) {
113
+ (_c = adapter.state.toDevRev) === null || _c === void 0 ? void 0 : _c.attachmentsMetadata.lastProcessedAttachmentsIdsList.push(attachment.id);
114
+ }
115
+ return null; // Return null for successful processing
116
+ })
117
+ .catch((error) => {
118
+ console.warn(`Skipping attachment with ID ${attachment.id} due to error: ${error}`);
119
+ return null; // Return null for errors too
120
+ });
121
+ promises.push(promise);
122
+ }
123
+ // Wait for all promises to settle and check for rate limiting
124
+ const results = await Promise.all(promises);
125
+ // Check if any of the results indicate rate limiting
126
+ const rateLimit = results.find(result => result === null || result === void 0 ? void 0 : result.delay);
127
+ if (rateLimit) {
128
+ // Return the delay information to the caller
129
+ return { delay: rateLimit.delay };
130
+ }
131
+ if (adapter.state.toDevRev) {
132
+ // Update the last processed batch index
133
+ adapter.state.toDevRev.attachmentsMetadata.lastProcessed = i + 1;
134
+ // Reset successfullyProcessedAttachments list
135
+ adapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList.length = 0;
136
+ }
137
+ }
138
+ return {};
139
+ };
42
140
  this.event = event;
43
141
  this.options = options;
44
142
  this.adapterState = adapterState;
@@ -88,7 +186,7 @@ class WorkerAdapter {
88
186
  getRepo(itemType) {
89
187
  const repo = this.repos.find((repo) => repo.itemType === itemType);
90
188
  if (!repo) {
91
- console.error(`Repo not found for item type: ${itemType}.`);
189
+ console.error(`Repo for item type ${itemType} not found.`);
92
190
  return;
93
191
  }
94
192
  return repo;
@@ -117,7 +215,14 @@ class WorkerAdapter {
117
215
  }
118
216
  // We want to upload all the repos before emitting the event, except for the external sync units done event
119
217
  if (newEventType !== extraction_1.ExtractorEventType.ExtractionExternalSyncUnitsDone) {
120
- await this.uploadAllRepos();
218
+ try {
219
+ await this.uploadAllRepos();
220
+ }
221
+ catch (error) {
222
+ console.error('Error while uploading repos', error);
223
+ node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage(workers_1.WorkerMessageSubject.WorkerMessageExit);
224
+ return;
225
+ }
121
226
  }
122
227
  // If the extraction is done, we want to save the timestamp of the last successful sync
123
228
  if (newEventType === extraction_1.ExtractorEventType.ExtractionAttachmentsDone) {
@@ -164,7 +269,10 @@ class WorkerAdapter {
164
269
  }
165
270
  async uploadAllRepos() {
166
271
  for (const repo of this.repos) {
167
- await repo.upload();
272
+ const error = await repo.upload();
273
+ if (error) {
274
+ throw error;
275
+ }
168
276
  }
169
277
  }
170
278
  handleTimeout() {
@@ -501,7 +609,7 @@ class WorkerAdapter {
501
609
  event: this.event,
502
610
  });
503
611
  if (error) {
504
- console.warn('Error while streaming attachment', error === null || error === void 0 ? void 0 : error.message);
612
+ console.warn('Error while streaming attachment', error);
505
613
  return { error };
506
614
  }
507
615
  else if (delay) {
@@ -573,27 +681,35 @@ class WorkerAdapter {
573
681
  * Streams the attachments to the DevRev platform.
574
682
  * The attachments are streamed to the platform and the artifact information is returned.
575
683
  * @param {{ stream, processors }: { stream: ExternalSystemAttachmentStreamingFunction, processors?: ExternalSystemAttachmentProcessors }} Params - The parameters to stream the attachments
576
- * @returns {Promise<StreamAttachmentsReturnType>} - The response object containing the ssoAttachment artifact information
684
+ * @returns {Promise<StreamAttachmentsReturnType>} - The response object containing the ssorAttachment artifact information
577
685
  * or error information if there was an error
578
686
  */
579
- async streamAttachments({ stream, processors, }) {
580
- var _a, _b, _c, _d;
687
+ async streamAttachments({ stream, processors, batchSize = 1, // By default, we want to stream one attachment at a time
688
+ }) {
689
+ var _a, _b;
690
+ if (batchSize <= 0) {
691
+ const error = new Error(`Invalid attachments batch size: ${batchSize}. Batch size must be greater than 0.`);
692
+ console.error(error.message);
693
+ return { error };
694
+ }
581
695
  const repos = [
582
696
  {
583
697
  itemType: 'ssor_attachment',
584
698
  },
585
699
  ];
586
700
  this.initializeRepos(repos);
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) {
701
+ // If there are no attachments metadata artifact IDs in state, finish here
702
+ if (!((_b = (_a = this.state.toDevRev) === null || _a === void 0 ? void 0 : _a.attachmentsMetadata) === null || _b === void 0 ? void 0 : _b.artifactIds) ||
703
+ this.state.toDevRev.attachmentsMetadata.artifactIds.length === 0) {
590
704
  console.log(`No attachments metadata artifact IDs found in state.`);
591
705
  return;
592
706
  }
593
707
  else {
594
- console.log(`Found ${attachmentsMetadataArtifactIds.length} attachments metadata artifact IDs in state.`);
708
+ console.log(`Found ${this.state.toDevRev.attachmentsMetadata.artifactIds.length} attachments metadata artifact IDs in state.`);
595
709
  }
596
- for (const attachmentsMetadataArtifactId of attachmentsMetadataArtifactIds) {
710
+ // Loop through the attachments metadata artifact IDs
711
+ while (this.state.toDevRev.attachmentsMetadata.artifactIds.length > 0) {
712
+ const attachmentsMetadataArtifactId = this.state.toDevRev.attachmentsMetadata.artifactIds[0];
597
713
  console.log(`Started processing attachments for attachments metadata artifact ID: ${attachmentsMetadataArtifactId}.`);
598
714
  const { attachments, error } = await this.uploader.getAttachmentsFromArtifactId({
599
715
  artifact: attachmentsMetadataArtifactId,
@@ -604,43 +720,44 @@ class WorkerAdapter {
604
720
  }
605
721
  if (!attachments || attachments.length === 0) {
606
722
  console.warn(`No attachments found for artifact ID: ${attachmentsMetadataArtifactId}.`);
723
+ // Remove empty artifact and reset lastProcessed
724
+ this.state.toDevRev.attachmentsMetadata.artifactIds.shift();
725
+ this.state.toDevRev.attachmentsMetadata.lastProcessed = 0;
607
726
  continue;
608
727
  }
609
728
  console.log(`Found ${attachments.length} attachments for artifact ID: ${attachmentsMetadataArtifactId}.`);
729
+ // Use the reducer to split into batches.
730
+ let reducer;
731
+ // Use the iterator to process each batch, streaming all attachments inside one batch in parallel.
732
+ let iterator;
610
733
  if (processors) {
611
734
  console.log(`Using custom processors for attachments.`);
612
- const { reducer, iterator } = processors;
613
- const reducedAttachments = reducer({ attachments, adapter: this });
614
- const response = await iterator({
615
- reducedAttachments,
616
- adapter: this,
617
- stream,
618
- });
619
- if ((response === null || response === void 0 ? void 0 : response.delay) || (response === null || response === void 0 ? void 0 : response.error)) {
620
- return response;
621
- }
735
+ reducer = processors.reducer;
736
+ iterator = processors.iterator;
622
737
  }
623
738
  else {
624
739
  console.log(`Using default processors for attachments.`);
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);
626
- for (const attachment of attachmentsToProcess) {
627
- const response = await this.processAttachment(attachment, stream);
628
- if (response === null || response === void 0 ? void 0 : response.delay) {
629
- return response;
630
- }
631
- else if (response === null || response === void 0 ? void 0 : response.error) {
632
- console.warn(`Skipping attachment with ID ${attachment.id} due to error.`);
633
- }
634
- if (this.state.toDevRev) {
635
- this.state.toDevRev.attachmentsMetadata.lastProcessed += 1;
636
- }
637
- }
740
+ reducer = this
741
+ .defaultAttachmentsReducer;
742
+ iterator = this
743
+ .defaultAttachmentsIterator;
638
744
  }
639
- if (this.state.toDevRev) {
640
- console.log(`Finished processing attachments for artifact ID. Setting last processed to 0 and removing artifact ID from state.`);
641
- this.state.toDevRev.attachmentsMetadata.artifactIds.shift();
642
- this.state.toDevRev.attachmentsMetadata.lastProcessed = 0;
745
+ const reducedAttachments = reducer({
746
+ attachments,
747
+ adapter: this,
748
+ batchSize,
749
+ });
750
+ const response = await iterator({
751
+ reducedAttachments,
752
+ adapter: this,
753
+ stream,
754
+ });
755
+ if ((response === null || response === void 0 ? void 0 : response.delay) || (response === null || response === void 0 ? void 0 : response.error)) {
756
+ return response;
643
757
  }
758
+ console.log(`Finished processing all attachments for artifact ID: ${attachmentsMetadataArtifactId}.`);
759
+ this.state.toDevRev.attachmentsMetadata.artifactIds.shift();
760
+ this.state.toDevRev.attachmentsMetadata.lastProcessed = 0;
644
761
  }
645
762
  return;
646
763
  }
@@ -0,0 +1 @@
1
+ export {};