Skip to content

Commit

Permalink
Added download method for returning stream
Browse files Browse the repository at this point in the history
  • Loading branch information
yifanplanet committed Nov 27, 2023
1 parent 3908446 commit 2b4c7bb
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 5 deletions.
37 changes: 37 additions & 0 deletions src/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,4 +244,41 @@ export default class APIClient {
// Return the raw buffer
return response.buffer();
}

async requestStream(
options: RequestOptionsParams
): Promise<NodeJS.ReadableStream> {
const req = this.newRequest(options);
const controller: AbortController = new AbortController();
const timeout = setTimeout(() => {
controller.abort();
throw new NylasSdkTimeoutError(req.url, this.timeout);
}, this.timeout);

const response = await fetch(req, { signal: controller.signal });
clearTimeout(timeout);

if (typeof response === 'undefined') {
throw new Error('Failed to fetch response');
}

// Handle error response
if (response.status > 299) {
const text = await response.text();
let error: Error;
try {
const parsedError = JSON.parse(text);
const camelCaseError = objKeysToCamelCase(parsedError);
error = new NylasApiError(camelCaseError, response.status);
} catch (e) {
throw new Error(
`Received an error but could not parse response from the server: ${text}`
);
}

throw error;
}

return response.body;
}
}
35 changes: 32 additions & 3 deletions src/resources/attachments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,45 @@ export class Attachments extends Resource {
overrides,
});
}

/**
* Returns an attachment by ID.
* @return The Attachment file in binary format
* Download the attachment data
*
* This method returns a NodeJS.ReadableStream which can be used to stream the attachment data.
* This is particularly useful for handling large attachments efficiently, as it avoids loading
* the entire file into memory. The stream can be piped to a file stream or used in any other way
* that Node.js streams are typically used.
*
* @param identifier Grant ID or email account to query
* @param attachmentId The id of the attachment to download.
* @param queryParams The query parameters to include in the request
* @returns {NodeJS.ReadableStream} The ReadableStream containing the file data.
*/
public download({
identifier,
attachmentId,
queryParams,
overrides,
}: DownloadAttachmentParams & Overrides): Promise<NodeJS.ReadableStream> {
return this._getStream({
path: `/v3/grants/${identifier}/attachments/${attachmentId}/download`,
queryParams,
overrides,
});
}

/**
* Download the attachment as a byte array
* @param identifier Grant ID or email account to query
* @param attachmentId The id of the attachment to download.
* @param queryParams The query parameters to include in the request
* @return The raw file data
*/
public downloadBytes({
identifier,
attachmentId,
queryParams,
overrides,
}: DownloadAttachmentParams & Overrides): Promise<Buffer> {
return super._getRaw({
path: `/v3/grants/${identifier}/attachments/${attachmentId}/download`,
Expand Down
13 changes: 13 additions & 0 deletions src/resources/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,19 @@ export class Resource {
overrides,
});
}

protected _getStream({
path,
queryParams,
overrides,
}: FindParams<void>): Promise<NodeJS.ReadableStream> {
return this.apiClient.requestStream({
method: 'GET',
path,
queryParams,
overrides,
});
}
}

type ListYieldReturn<T> = T & {
Expand Down
34 changes: 32 additions & 2 deletions tests/resources/attachments.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import APIClient from '../../src/apiClient';
import { Attachments } from '../../src/resources/attachments';
import { Readable } from 'stream';
jest.mock('../src/apiClient');

describe('Attachments', () => {
Expand All @@ -16,6 +17,9 @@ describe('Attachments', () => {
attachments = new Attachments(apiClient);
apiClient.request.mockResolvedValue({});
apiClient.requestRaw.mockResolvedValue(Buffer.from(''));

const mockStream = new Readable();
apiClient.requestStream.mockResolvedValue(Promise.resolve(mockStream));
});

describe('find', () => {
Expand Down Expand Up @@ -44,8 +48,34 @@ describe('Attachments', () => {
});
});

describe('downloadBytes', () => {
it('should call apiClient.requestRaw with the correct params for downloading an attachment as bytes', async () => {
await attachments.downloadBytes({
identifier: 'id123',
attachmentId: 'attach123',
queryParams: {
messageId: 'message123',
},
overrides: {
apiUri: 'https://test.api.nylas.com',
},
});

expect(apiClient.requestRaw).toHaveBeenCalledWith({
method: 'GET',
path: '/v3/grants/id123/attachments/attach123/download',
queryParams: {
messageId: 'message123',
},
overrides: {
apiUri: 'https://test.api.nylas.com',
},
});
});
});

describe('download', () => {
it('should call apiClient.requestRaw with the correct params for downloading an attachment', async () => {
it('should call apiClient.requestStream with the correct params for streaming an attachment', async () => {
await attachments.download({
identifier: 'id123',
attachmentId: 'attach123',
Expand All @@ -57,7 +87,7 @@ describe('Attachments', () => {
},
});

expect(apiClient.requestRaw).toHaveBeenCalledWith({
expect(apiClient.requestStream).toHaveBeenCalledWith({
method: 'GET',
path: '/v3/grants/id123/attachments/attach123/download',
queryParams: {
Expand Down

0 comments on commit 2b4c7bb

Please sign in to comment.