@arela/uploader 0.2.7 → 0.2.9

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.
@@ -1,6 +1,8 @@
1
1
  import { Blob } from 'buffer';
2
2
  import { FormData } from 'formdata-node';
3
3
  import fs from 'fs';
4
+ import { Agent } from 'http';
5
+ import { Agent as HttpsAgent } from 'https';
4
6
  import fetch from 'node-fetch';
5
7
  import path from 'path';
6
8
 
@@ -16,6 +18,36 @@ export class ApiUploadService extends BaseUploadService {
16
18
  super();
17
19
  this.baseUrl = appConfig.api.baseUrl;
18
20
  this.token = appConfig.api.token;
21
+
22
+ // Get API connection settings from config/environment
23
+ const maxApiConnections = parseInt(process.env.MAX_API_CONNECTIONS) || 10;
24
+ const connectionTimeout =
25
+ parseInt(process.env.API_CONNECTION_TIMEOUT) || 60000;
26
+
27
+ // Initialize HTTP agents optimized for multiple API replicas
28
+ this.httpAgent = new Agent({
29
+ keepAlive: true,
30
+ keepAliveMsecs: 30000,
31
+ maxSockets: maxApiConnections, // Match your API replica count
32
+ maxFreeSockets: Math.ceil(maxApiConnections / 2),
33
+ maxTotalSockets: maxApiConnections + 5, // Buffer for peak usage
34
+ timeout: connectionTimeout,
35
+ scheduling: 'fifo', // First-in-first-out scheduling
36
+ });
37
+
38
+ this.httpsAgent = new HttpsAgent({
39
+ keepAlive: true,
40
+ keepAliveMsecs: 30000,
41
+ maxSockets: maxApiConnections, // Match your API replica count
42
+ maxFreeSockets: Math.ceil(maxApiConnections / 2),
43
+ maxTotalSockets: maxApiConnections + 5, // Buffer for peak usage
44
+ timeout: connectionTimeout,
45
+ scheduling: 'fifo', // First-in-first-out scheduling
46
+ });
47
+
48
+ console.log(
49
+ `🔗 HTTP Agent configured for ${maxApiConnections} concurrent API connections`,
50
+ );
19
51
  }
20
52
 
21
53
  /**
@@ -27,12 +59,31 @@ export class ApiUploadService extends BaseUploadService {
27
59
  async upload(files, options) {
28
60
  const formData = new FormData();
29
61
 
30
- // Add files to form data
31
- files.forEach((file) => {
32
- const fileBuffer = fs.readFileSync(file.path);
33
- const blob = new Blob([fileBuffer], { type: file.contentType });
34
- formData.append('files', blob, file.name);
35
- });
62
+ // Add files to form data asynchronously
63
+ for (const file of files) {
64
+ try {
65
+ // Check file size for streaming vs buffer approach
66
+ const stats = await fs.promises.stat(file.path);
67
+ const fileSizeThreshold = 10 * 1024 * 1024; // 10MB threshold
68
+
69
+ if (stats.size > fileSizeThreshold) {
70
+ // Use streaming for large files
71
+ const fileStream = fs.createReadStream(file.path);
72
+ formData.append('files', fileStream, {
73
+ filename: file.name,
74
+ contentType: file.contentType,
75
+ knownLength: stats.size,
76
+ });
77
+ } else {
78
+ // Use buffer for smaller files
79
+ const fileBuffer = await fs.promises.readFile(file.path);
80
+ const blob = new Blob([fileBuffer], { type: file.contentType });
81
+ formData.append('files', blob, file.name);
82
+ }
83
+ } catch (error) {
84
+ throw new Error(`Failed to read file ${file.path}: ${error.message}`);
85
+ }
86
+ }
36
87
 
37
88
  // Add configuration parameters
38
89
  if (appConfig.supabase.bucket) {
@@ -61,6 +112,7 @@ export class ApiUploadService extends BaseUploadService {
61
112
  formData.append('clientVersion', appConfig.packageVersion);
62
113
 
63
114
  try {
115
+ const isHttps = this.baseUrl.startsWith('https');
64
116
  const response = await fetch(
65
117
  `${this.baseUrl}/api/storage/batch-upload-and-process`,
66
118
  {
@@ -69,6 +121,7 @@ export class ApiUploadService extends BaseUploadService {
69
121
  'x-api-key': this.token,
70
122
  },
71
123
  body: formData,
124
+ agent: isHttps ? this.httpsAgent : this.httpAgent,
72
125
  },
73
126
  );
74
127
 
@@ -81,8 +134,16 @@ export class ApiUploadService extends BaseUploadService {
81
134
 
82
135
  const result = await response.json();
83
136
 
137
+ // Determine if the operation was successful
138
+ // Success means at least one file was uploaded successfully, even if some failed
139
+ const hasUploads = result.uploaded && result.uploaded.length > 0;
140
+ const hasErrors = result.errors && result.errors.length > 0;
141
+ // Consider it a success if there are uploads, or if there are no errors at all
142
+ const success =
143
+ hasUploads || (!hasErrors && result.stats?.totalFiles > 0);
144
+
84
145
  // Normalize response format to match DatabaseService expectations
85
- return { success: true, data: result };
146
+ return { success, data: result };
86
147
  } catch (fetchError) {
87
148
  // Return normalized error format
88
149
  return { success: false, error: fetchError.message };
@@ -99,10 +160,12 @@ export class ApiUploadService extends BaseUploadService {
99
160
  }
100
161
 
101
162
  try {
163
+ const isHttps = this.baseUrl.startsWith('https');
102
164
  const response = await fetch(`${this.baseUrl}/api/health`, {
103
165
  headers: {
104
166
  'x-api-key': this.token,
105
167
  },
168
+ agent: isHttps ? this.httpsAgent : this.httpAgent,
106
169
  });
107
170
 
108
171
  return response.ok;
@@ -88,10 +88,10 @@ export class SupabaseUploadService extends BaseUploadService {
88
88
  });
89
89
 
90
90
  if (error) {
91
- throw new Error(error.message);
91
+ return { success: false, error: error.message };
92
92
  }
93
93
 
94
- return data;
94
+ return { success: true, data };
95
95
  }
96
96
 
97
97
  /**