@unito/integration-sdk 2.0.2 → 2.1.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.
@@ -1301,6 +1301,33 @@ class Provider {
1301
1301
  },
1302
1302
  });
1303
1303
  }
1304
+ /**
1305
+ * Performs a PUT request to the provider with a Buffer body, typically used for sending binary data.
1306
+ *
1307
+ * IMPORTANT: This method should ONLY be used as a last resort when FormData cannot be used.
1308
+ * It bypasses normal form handling and is used to **manually send chunked** binary data, which may not be appropriate
1309
+ * for all providers. Always be mindful not to load entire binary files in memory!
1310
+ *
1311
+ * Uses the prepareRequest function to get the base URL and any specific headers to add to the request and by default
1312
+ * adds the following headers:
1313
+ * - Content-Type: application/octet-stream
1314
+ * - Accept: application/json
1315
+ *
1316
+ * @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
1317
+ * @param body The Buffer containing the binary data to be sent.
1318
+ * @param options RequestOptions used to adjust the call made to the provider (use to override default headers).
1319
+ * @returns The {@link Response} extracted from the provider.
1320
+ */
1321
+ async putBuffer(endpoint, body, options) {
1322
+ return this.fetchWrapper(endpoint, body, {
1323
+ ...options,
1324
+ method: 'PUT',
1325
+ defaultHeaders: {
1326
+ 'Content-Type': 'application/octet-stream',
1327
+ Accept: 'application/json',
1328
+ },
1329
+ });
1330
+ }
1304
1331
  /**
1305
1332
  * Performs a PATCH request to the provider.
1306
1333
  *
@@ -1360,14 +1387,20 @@ class Provider {
1360
1387
  const { url: providerUrl, headers: providerHeaders } = await this.prepareRequest(options);
1361
1388
  const absoluteUrl = this.generateAbsoluteUrl(providerUrl, endpoint, options.queryParams);
1362
1389
  const headers = { ...options.defaultHeaders, ...providerHeaders, ...options.additionnalheaders };
1363
- let stringifiedBody = null;
1390
+ let fetchBody = null;
1364
1391
  if (body) {
1365
1392
  if (headers['Content-Type'] === 'application/x-www-form-urlencoded') {
1366
- stringifiedBody = new URLSearchParams(body).toString();
1393
+ fetchBody = new URLSearchParams(body).toString();
1367
1394
  }
1368
1395
  else if (headers['Content-Type'] === 'application/json' ||
1369
1396
  headers['Content-Type'] === 'application/json-patch+json') {
1370
- stringifiedBody = JSON.stringify(body);
1397
+ fetchBody = JSON.stringify(body);
1398
+ }
1399
+ else if (headers['Content-Type'] === 'application/octet-stream' && body instanceof Buffer) {
1400
+ fetchBody = body;
1401
+ }
1402
+ else {
1403
+ throw this.handleError(400, `Content type not supported: ${headers['Content-Type']}`, options);
1371
1404
  }
1372
1405
  }
1373
1406
  const callToProvider = async () => {
@@ -1376,7 +1409,7 @@ class Provider {
1376
1409
  response = await fetch(absoluteUrl, {
1377
1410
  method: options.method,
1378
1411
  headers,
1379
- body: stringifiedBody,
1412
+ body: fetchBody,
1380
1413
  ...(options.signal ? { signal: options.signal } : {}),
1381
1414
  });
1382
1415
  }
@@ -165,6 +165,24 @@ export declare class Provider {
165
165
  * @returns The {@link Response} extracted from the provider.
166
166
  */
167
167
  put<T>(endpoint: string, body: RequestBody, options: RequestOptions): Promise<Response<T>>;
168
+ /**
169
+ * Performs a PUT request to the provider with a Buffer body, typically used for sending binary data.
170
+ *
171
+ * IMPORTANT: This method should ONLY be used as a last resort when FormData cannot be used.
172
+ * It bypasses normal form handling and is used to **manually send chunked** binary data, which may not be appropriate
173
+ * for all providers. Always be mindful not to load entire binary files in memory!
174
+ *
175
+ * Uses the prepareRequest function to get the base URL and any specific headers to add to the request and by default
176
+ * adds the following headers:
177
+ * - Content-Type: application/octet-stream
178
+ * - Accept: application/json
179
+ *
180
+ * @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
181
+ * @param body The Buffer 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
+ putBuffer<T>(endpoint: string, body: Buffer, options: RequestOptions): Promise<Response<T>>;
168
186
  /**
169
187
  * Performs a PATCH request to the provider.
170
188
  *
@@ -181,6 +181,33 @@ export class Provider {
181
181
  },
182
182
  });
183
183
  }
184
+ /**
185
+ * Performs a PUT request to the provider with a Buffer body, typically used for sending binary data.
186
+ *
187
+ * IMPORTANT: This method should ONLY be used as a last resort when FormData cannot be used.
188
+ * It bypasses normal form handling and is used to **manually send chunked** binary data, which may not be appropriate
189
+ * for all providers. Always be mindful not to load entire binary files in memory!
190
+ *
191
+ * Uses the prepareRequest function to get the base URL and any specific headers to add to the request and by default
192
+ * adds the following headers:
193
+ * - Content-Type: application/octet-stream
194
+ * - Accept: application/json
195
+ *
196
+ * @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
197
+ * @param body The Buffer containing the binary data to be sent.
198
+ * @param options RequestOptions used to adjust the call made to the provider (use to override default headers).
199
+ * @returns The {@link Response} extracted from the provider.
200
+ */
201
+ async putBuffer(endpoint, body, options) {
202
+ return this.fetchWrapper(endpoint, body, {
203
+ ...options,
204
+ method: 'PUT',
205
+ defaultHeaders: {
206
+ 'Content-Type': 'application/octet-stream',
207
+ Accept: 'application/json',
208
+ },
209
+ });
210
+ }
184
211
  /**
185
212
  * Performs a PATCH request to the provider.
186
213
  *
@@ -240,14 +267,20 @@ export class Provider {
240
267
  const { url: providerUrl, headers: providerHeaders } = await this.prepareRequest(options);
241
268
  const absoluteUrl = this.generateAbsoluteUrl(providerUrl, endpoint, options.queryParams);
242
269
  const headers = { ...options.defaultHeaders, ...providerHeaders, ...options.additionnalheaders };
243
- let stringifiedBody = null;
270
+ let fetchBody = null;
244
271
  if (body) {
245
272
  if (headers['Content-Type'] === 'application/x-www-form-urlencoded') {
246
- stringifiedBody = new URLSearchParams(body).toString();
273
+ fetchBody = new URLSearchParams(body).toString();
247
274
  }
248
275
  else if (headers['Content-Type'] === 'application/json' ||
249
276
  headers['Content-Type'] === 'application/json-patch+json') {
250
- stringifiedBody = JSON.stringify(body);
277
+ fetchBody = JSON.stringify(body);
278
+ }
279
+ else if (headers['Content-Type'] === 'application/octet-stream' && body instanceof Buffer) {
280
+ fetchBody = body;
281
+ }
282
+ else {
283
+ throw this.handleError(400, `Content type not supported: ${headers['Content-Type']}`, options);
251
284
  }
252
285
  }
253
286
  const callToProvider = async () => {
@@ -256,7 +289,7 @@ export class Provider {
256
289
  response = await fetch(absoluteUrl, {
257
290
  method: options.method,
258
291
  headers,
259
- body: stringifiedBody,
292
+ body: fetchBody,
260
293
  ...(options.signal ? { signal: options.signal } : {}),
261
294
  });
262
295
  }
@@ -310,6 +310,38 @@ describe('Provider', () => {
310
310
  ]);
311
311
  assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
312
312
  });
313
+ it('putBuffer with Buffer body', async (context) => {
314
+ const response = new Response('{"data": "value"}', {
315
+ status: 201,
316
+ headers: { 'Content-Type': 'application/json' },
317
+ });
318
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
319
+ const buffer = Buffer.from('binary data content');
320
+ // What matters is that the body of put is a buffer
321
+ const actualResponse = await provider.putBuffer('endpoint/123', buffer, {
322
+ credentials: { apiKey: 'apikey#1111' },
323
+ logger: logger,
324
+ signal: new AbortController().signal,
325
+ additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/octet-stream' },
326
+ });
327
+ assert.equal(fetchMock.mock.calls.length, 1);
328
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
329
+ 'www.myApi.com/endpoint/123',
330
+ {
331
+ method: 'PUT',
332
+ body: buffer,
333
+ signal: new AbortController().signal,
334
+ headers: {
335
+ 'Content-Type': 'application/octet-stream',
336
+ Accept: 'application/json',
337
+ 'X-Custom-Provider-Header': 'value',
338
+ 'X-Provider-Credential-Header': 'apikey#1111',
339
+ 'X-Additional-Header': 'value1',
340
+ },
341
+ },
342
+ ]);
343
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
344
+ });
313
345
  it('patch with query params', async (context) => {
314
346
  const response = new Response('{"data": "value"}', {
315
347
  status: 201,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unito/integration-sdk",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Integration SDK",
5
5
  "type": "module",
6
6
  "types": "dist/src/index.d.ts",
@@ -274,6 +274,34 @@ export class Provider {
274
274
  });
275
275
  }
276
276
 
277
+ /**
278
+ * Performs a PUT request to the provider with a Buffer body, typically used for sending binary data.
279
+ *
280
+ * IMPORTANT: This method should ONLY be used as a last resort when FormData cannot be used.
281
+ * It bypasses normal form handling and is used to **manually send chunked** binary data, which may not be appropriate
282
+ * for all providers. Always be mindful not to load entire binary files in memory!
283
+ *
284
+ * Uses the prepareRequest function to get the base URL and any specific headers to add to the request and by default
285
+ * adds the following headers:
286
+ * - Content-Type: application/octet-stream
287
+ * - Accept: application/json
288
+ *
289
+ * @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
290
+ * @param body The Buffer containing the binary data to be sent.
291
+ * @param options RequestOptions used to adjust the call made to the provider (use to override default headers).
292
+ * @returns The {@link Response} extracted from the provider.
293
+ */
294
+ public async putBuffer<T>(endpoint: string, body: Buffer, options: RequestOptions): Promise<Response<T>> {
295
+ return this.fetchWrapper<T>(endpoint, body, {
296
+ ...options,
297
+ method: 'PUT',
298
+ defaultHeaders: {
299
+ 'Content-Type': 'application/octet-stream',
300
+ Accept: 'application/json',
301
+ },
302
+ });
303
+ }
304
+
277
305
  /**
278
306
  * Performs a PATCH request to the provider.
279
307
  *
@@ -335,23 +363,27 @@ export class Provider {
335
363
 
336
364
  private async fetchWrapper<T>(
337
365
  endpoint: string,
338
- body: RequestBody | null,
366
+ body: RequestBody | Buffer | null,
339
367
  options: RequestOptions & { defaultHeaders: { 'Content-Type'?: string; Accept?: string }; method: string },
340
368
  ): Promise<Response<T>> {
341
369
  const { url: providerUrl, headers: providerHeaders } = await this.prepareRequest(options);
342
370
  const absoluteUrl = this.generateAbsoluteUrl(providerUrl, endpoint, options.queryParams);
343
371
  const headers = { ...options.defaultHeaders, ...providerHeaders, ...options.additionnalheaders };
344
372
 
345
- let stringifiedBody: string | null = null;
373
+ let fetchBody: string | Buffer | null = null;
346
374
 
347
375
  if (body) {
348
376
  if (headers['Content-Type'] === 'application/x-www-form-urlencoded') {
349
- stringifiedBody = new URLSearchParams(body as Record<string, string>).toString();
377
+ fetchBody = new URLSearchParams(body as Record<string, string>).toString();
350
378
  } else if (
351
379
  headers['Content-Type'] === 'application/json' ||
352
380
  headers['Content-Type'] === 'application/json-patch+json'
353
381
  ) {
354
- stringifiedBody = JSON.stringify(body);
382
+ fetchBody = JSON.stringify(body);
383
+ } else if (headers['Content-Type'] === 'application/octet-stream' && body instanceof Buffer) {
384
+ fetchBody = body;
385
+ } else {
386
+ throw this.handleError(400, `Content type not supported: ${headers['Content-Type']}`, options);
355
387
  }
356
388
  }
357
389
 
@@ -362,7 +394,7 @@ export class Provider {
362
394
  response = await fetch(absoluteUrl, {
363
395
  method: options.method,
364
396
  headers,
365
- body: stringifiedBody,
397
+ body: fetchBody,
366
398
  ...(options.signal ? { signal: options.signal } : {}),
367
399
  });
368
400
  } catch (error) {
@@ -375,6 +375,43 @@ describe('Provider', () => {
375
375
  assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
376
376
  });
377
377
 
378
+ it('putBuffer with Buffer body', async context => {
379
+ const response = new Response('{"data": "value"}', {
380
+ status: 201,
381
+ headers: { 'Content-Type': 'application/json' },
382
+ });
383
+
384
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
385
+
386
+ const buffer = Buffer.from('binary data content');
387
+
388
+ // What matters is that the body of put is a buffer
389
+ const actualResponse = await provider.putBuffer('endpoint/123', buffer, {
390
+ credentials: { apiKey: 'apikey#1111' },
391
+ logger: logger,
392
+ signal: new AbortController().signal,
393
+ additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/octet-stream' },
394
+ });
395
+
396
+ assert.equal(fetchMock.mock.calls.length, 1);
397
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
398
+ 'www.myApi.com/endpoint/123',
399
+ {
400
+ method: 'PUT',
401
+ body: buffer,
402
+ signal: new AbortController().signal,
403
+ headers: {
404
+ 'Content-Type': 'application/octet-stream',
405
+ Accept: 'application/json',
406
+ 'X-Custom-Provider-Header': 'value',
407
+ 'X-Provider-Credential-Header': 'apikey#1111',
408
+ 'X-Additional-Header': 'value1',
409
+ },
410
+ },
411
+ ]);
412
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
413
+ });
414
+
378
415
  it('patch with query params', async context => {
379
416
  const response = new Response('{"data": "value"}', {
380
417
  status: 201,