@unito/integration-sdk 2.3.13 → 2.3.15

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.
@@ -1359,7 +1359,7 @@ class Provider {
1359
1359
  if (body.error) {
1360
1360
  reject(this.handleError(400, body.error.message, options));
1361
1361
  }
1362
- resolve({ status: 201, headers: response.headers, body: body });
1362
+ resolve({ status: 201, headers: response.headers, body });
1363
1363
  }
1364
1364
  catch (error) {
1365
1365
  reject(this.handleError(500, `Failed to parse response body: "${error}"`, options));
@@ -1369,6 +1369,21 @@ class Provider {
1369
1369
  request.on('error', error => {
1370
1370
  reject(this.handleError(400, `Error while calling the provider: "${error}"`, options));
1371
1371
  });
1372
+ if (options.signal) {
1373
+ const abortHandler = () => {
1374
+ request.destroy();
1375
+ reject(this.handleError(408, 'Timeout', options));
1376
+ };
1377
+ if (options.signal.aborted) {
1378
+ abortHandler();
1379
+ }
1380
+ options.signal.addEventListener('abort', abortHandler);
1381
+ request.on('close', () => {
1382
+ if (options.signal) {
1383
+ options.signal.removeEventListener('abort', abortHandler);
1384
+ }
1385
+ });
1386
+ }
1372
1387
  form.pipe(request);
1373
1388
  }
1374
1389
  catch (error) {
@@ -1378,6 +1393,116 @@ class Provider {
1378
1393
  };
1379
1394
  return this.rateLimiter ? this.rateLimiter(options, callToProvider) : callToProvider();
1380
1395
  }
1396
+ /**
1397
+ * Performs a POST request to the provider streaming a Readable directly without loading it into memory.
1398
+ *
1399
+ * @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
1400
+ * @param stream The Readable stream containing the binary data to be sent.
1401
+ * @param options RequestOptions used to adjust the call made to the provider (use to override default headers).
1402
+ * @returns The {@link Response} extracted from the provider.
1403
+ */
1404
+ async postStream(endpoint, stream, options) {
1405
+ const { url: providerUrl, headers: providerHeaders } = await this.prepareRequest(options);
1406
+ const absoluteUrl = this.generateAbsoluteUrl(providerUrl, endpoint, options.queryParams);
1407
+ const headers = {
1408
+ 'Content-Type': 'application/octet-stream',
1409
+ Accept: 'application/json',
1410
+ ...providerHeaders,
1411
+ ...options.additionnalheaders,
1412
+ };
1413
+ const callToProvider = async () => {
1414
+ return new Promise((resolve, reject) => {
1415
+ let isSettled = false; // Prevent double rejection
1416
+ const cleanup = () => {
1417
+ if (!stream.destroyed) {
1418
+ stream.destroy();
1419
+ }
1420
+ };
1421
+ const safeReject = (error) => {
1422
+ if (!isSettled) {
1423
+ isSettled = true;
1424
+ cleanup();
1425
+ reject(error);
1426
+ }
1427
+ };
1428
+ const safeResolve = (response) => {
1429
+ if (!isSettled) {
1430
+ isSettled = true;
1431
+ resolve(response);
1432
+ }
1433
+ };
1434
+ try {
1435
+ const urlObj = new URL(absoluteUrl);
1436
+ const requestOptions = {
1437
+ hostname: urlObj.hostname,
1438
+ path: urlObj.pathname + urlObj.search,
1439
+ method: 'POST',
1440
+ headers,
1441
+ timeout: 0,
1442
+ };
1443
+ const request = https.request(requestOptions, response => {
1444
+ response.setEncoding('utf8');
1445
+ let responseBody = '';
1446
+ response.on('data', chunk => {
1447
+ responseBody += chunk;
1448
+ });
1449
+ response.on('error', error => {
1450
+ safeReject(this.handleError(500, `Response stream error: "${error}"`, options));
1451
+ });
1452
+ response.on('end', () => {
1453
+ try {
1454
+ if (response.statusCode && response.statusCode >= 400) {
1455
+ safeReject(this.handleError(response.statusCode, responseBody, options));
1456
+ return;
1457
+ }
1458
+ const body = responseBody ? JSON.parse(responseBody) : undefined;
1459
+ safeResolve({
1460
+ status: response.statusCode || 200,
1461
+ headers: response.headers,
1462
+ body,
1463
+ });
1464
+ }
1465
+ catch (error) {
1466
+ safeReject(this.handleError(500, `Failed to parse response body: "${error}"`, options));
1467
+ }
1468
+ });
1469
+ });
1470
+ request.on('timeout', () => {
1471
+ request.destroy();
1472
+ safeReject(this.handleError(408, 'Request timeout', options));
1473
+ });
1474
+ request.on('error', error => {
1475
+ safeReject(this.handleError(500, `Error while calling the provider: "${error}"`, options));
1476
+ });
1477
+ stream.on('error', error => {
1478
+ request.destroy();
1479
+ safeReject(this.handleError(500, `Stream error: "${error}"`, options));
1480
+ });
1481
+ if (options.signal) {
1482
+ const abortHandler = () => {
1483
+ request.destroy();
1484
+ safeReject(this.handleError(408, 'Timeout', options));
1485
+ };
1486
+ if (options.signal.aborted) {
1487
+ abortHandler();
1488
+ }
1489
+ options.signal.addEventListener('abort', abortHandler);
1490
+ request.on('close', () => {
1491
+ if (options.signal) {
1492
+ options.signal.removeEventListener('abort', abortHandler);
1493
+ }
1494
+ });
1495
+ }
1496
+ // Stream the data directly without buffering
1497
+ stream.pipe(request);
1498
+ }
1499
+ catch (error) {
1500
+ safeReject(this.handleError(500, `Unexpected error while calling the provider: "${error}"`, options));
1501
+ }
1502
+ });
1503
+ };
1504
+ return this.rateLimiter ? this.rateLimiter(options, callToProvider) : callToProvider();
1505
+ }
1381
1506
  /**
1382
1507
  * Performs a PUT request to the provider.
1383
1508
  *
@@ -1,4 +1,6 @@
1
1
  import FormData from 'form-data';
2
+ import * as stream from 'stream';
3
+ import { IncomingHttpHeaders } from 'http';
2
4
  import * as HttpErrors from '../httpErrors.js';
3
5
  import { Credentials } from '../middlewares/credentials.js';
4
6
  import Logger from '../resources/logger.js';
@@ -54,7 +56,7 @@ export type RequestOptions = {
54
56
  export type Response<T> = {
55
57
  body: T;
56
58
  status: number;
57
- headers: Headers;
59
+ headers: Headers | IncomingHttpHeaders;
58
60
  };
59
61
  export type PreparedRequest = {
60
62
  url: string;
@@ -152,6 +154,15 @@ export declare class Provider {
152
154
  */
153
155
  post<T>(endpoint: string, body: RequestBody, options: RequestOptions): Promise<Response<T>>;
154
156
  postForm<T>(endpoint: string, form: FormData, options: RequestOptions): Promise<Response<T>>;
157
+ /**
158
+ * Performs a POST request to the provider streaming a Readable directly without loading it into memory.
159
+ *
160
+ * @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
161
+ * @param stream The Readable stream containing the binary data to be sent.
162
+ * @param options RequestOptions used to adjust the call made to the provider (use to override default headers).
163
+ * @returns The {@link Response} extracted from the provider.
164
+ */
165
+ postStream<T>(endpoint: string, stream: stream.Readable, options: RequestOptions): Promise<Response<T>>;
155
166
  /**
156
167
  * Performs a PUT request to the provider.
157
168
  *
@@ -140,7 +140,7 @@ export class Provider {
140
140
  if (body.error) {
141
141
  reject(this.handleError(400, body.error.message, options));
142
142
  }
143
- resolve({ status: 201, headers: response.headers, body: body });
143
+ resolve({ status: 201, headers: response.headers, body });
144
144
  }
145
145
  catch (error) {
146
146
  reject(this.handleError(500, `Failed to parse response body: "${error}"`, options));
@@ -150,6 +150,21 @@ export class Provider {
150
150
  request.on('error', error => {
151
151
  reject(this.handleError(400, `Error while calling the provider: "${error}"`, options));
152
152
  });
153
+ if (options.signal) {
154
+ const abortHandler = () => {
155
+ request.destroy();
156
+ reject(this.handleError(408, 'Timeout', options));
157
+ };
158
+ if (options.signal.aborted) {
159
+ abortHandler();
160
+ }
161
+ options.signal.addEventListener('abort', abortHandler);
162
+ request.on('close', () => {
163
+ if (options.signal) {
164
+ options.signal.removeEventListener('abort', abortHandler);
165
+ }
166
+ });
167
+ }
153
168
  form.pipe(request);
154
169
  }
155
170
  catch (error) {
@@ -159,6 +174,116 @@ export class Provider {
159
174
  };
160
175
  return this.rateLimiter ? this.rateLimiter(options, callToProvider) : callToProvider();
161
176
  }
177
+ /**
178
+ * Performs a POST request to the provider streaming a Readable directly without loading it into memory.
179
+ *
180
+ * @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
181
+ * @param stream The Readable stream containing the binary data to be sent.
182
+ * @param options RequestOptions used to adjust the call made to the provider (use to override default headers).
183
+ * @returns The {@link Response} extracted from the provider.
184
+ */
185
+ async postStream(endpoint, stream, options) {
186
+ const { url: providerUrl, headers: providerHeaders } = await this.prepareRequest(options);
187
+ const absoluteUrl = this.generateAbsoluteUrl(providerUrl, endpoint, options.queryParams);
188
+ const headers = {
189
+ 'Content-Type': 'application/octet-stream',
190
+ Accept: 'application/json',
191
+ ...providerHeaders,
192
+ ...options.additionnalheaders,
193
+ };
194
+ const callToProvider = async () => {
195
+ return new Promise((resolve, reject) => {
196
+ let isSettled = false; // Prevent double rejection
197
+ const cleanup = () => {
198
+ if (!stream.destroyed) {
199
+ stream.destroy();
200
+ }
201
+ };
202
+ const safeReject = (error) => {
203
+ if (!isSettled) {
204
+ isSettled = true;
205
+ cleanup();
206
+ reject(error);
207
+ }
208
+ };
209
+ const safeResolve = (response) => {
210
+ if (!isSettled) {
211
+ isSettled = true;
212
+ resolve(response);
213
+ }
214
+ };
215
+ try {
216
+ const urlObj = new URL(absoluteUrl);
217
+ const requestOptions = {
218
+ hostname: urlObj.hostname,
219
+ path: urlObj.pathname + urlObj.search,
220
+ method: 'POST',
221
+ headers,
222
+ timeout: 0,
223
+ };
224
+ const request = https.request(requestOptions, response => {
225
+ response.setEncoding('utf8');
226
+ let responseBody = '';
227
+ response.on('data', chunk => {
228
+ responseBody += chunk;
229
+ });
230
+ response.on('error', error => {
231
+ safeReject(this.handleError(500, `Response stream error: "${error}"`, options));
232
+ });
233
+ response.on('end', () => {
234
+ try {
235
+ if (response.statusCode && response.statusCode >= 400) {
236
+ safeReject(this.handleError(response.statusCode, responseBody, options));
237
+ return;
238
+ }
239
+ const body = responseBody ? JSON.parse(responseBody) : undefined;
240
+ safeResolve({
241
+ status: response.statusCode || 200,
242
+ headers: response.headers,
243
+ body,
244
+ });
245
+ }
246
+ catch (error) {
247
+ safeReject(this.handleError(500, `Failed to parse response body: "${error}"`, options));
248
+ }
249
+ });
250
+ });
251
+ request.on('timeout', () => {
252
+ request.destroy();
253
+ safeReject(this.handleError(408, 'Request timeout', options));
254
+ });
255
+ request.on('error', error => {
256
+ safeReject(this.handleError(500, `Error while calling the provider: "${error}"`, options));
257
+ });
258
+ stream.on('error', error => {
259
+ request.destroy();
260
+ safeReject(this.handleError(500, `Stream error: "${error}"`, options));
261
+ });
262
+ if (options.signal) {
263
+ const abortHandler = () => {
264
+ request.destroy();
265
+ safeReject(this.handleError(408, 'Timeout', options));
266
+ };
267
+ if (options.signal.aborted) {
268
+ abortHandler();
269
+ }
270
+ options.signal.addEventListener('abort', abortHandler);
271
+ request.on('close', () => {
272
+ if (options.signal) {
273
+ options.signal.removeEventListener('abort', abortHandler);
274
+ }
275
+ });
276
+ }
277
+ // Stream the data directly without buffering
278
+ stream.pipe(request);
279
+ }
280
+ catch (error) {
281
+ safeReject(this.handleError(500, `Unexpected error while calling the provider: "${error}"`, options));
282
+ }
283
+ });
284
+ };
285
+ return this.rateLimiter ? this.rateLimiter(options, callToProvider) : callToProvider();
286
+ }
162
287
  /**
163
288
  * Performs a PUT request to the provider.
164
289
  *