@devrev/airsync-sdk 2.0.0 → 2.1.0-beta.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AirSync SDK
2
2
 
3
- [![Coverage Status](https://coveralls.io/repos/github/devrev/adaas-sdk/badge.svg?branch=main&t=s4Otlm)](https://coveralls.io/github/devrev/adaas-sdk?branch=main)
3
+ [![Coverage Status](https://coveralls.io/repos/github/devrev/airsync-sdk/badge.svg?branch=main&t=s4Otlm)](https://coveralls.io/github/devrev/airsync-sdk?branch=main)
4
4
 
5
5
  TypeScript SDK for building AirSync snap-ins (connectors) that sync data between external systems and DevRev. It handles the AirSync control protocol for you: worker threads, timeouts, state, artifact uploads, attachment streaming and loading.
6
6
 
@@ -4,6 +4,7 @@ export declare const STATELESS_LOADING_EVENT_TYPES: EventType[];
4
4
  export declare const STATELESS_EVENT_TYPES: EventType[];
5
5
  export declare const ARTIFACT_BATCH_SIZE = 2000;
6
6
  export declare const MAX_DEVREV_ARTIFACT_SIZE: number;
7
+ export declare const S3_MULTIPART_CHUNK_SIZE: number;
7
8
  export declare const MAX_DEVREV_FILENAME_LENGTH = 256;
8
9
  export declare const MAX_DEVREV_FILENAME_EXTENSION_LENGTH = 20;
9
10
  export declare const MAX_EVENT_SIZE_BYTES = 200000;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/common/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAIhD,eAAO,MAAM,gCAAgC,aAI5C,CAAC;AAEF,eAAO,MAAM,6BAA6B,aAGzC,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAGjC,CAAC;AAEF,eAAO,MAAM,mBAAmB,OAAO,CAAC;AACxC,eAAO,MAAM,wBAAwB,QAAyB,CAAC;AAC/D,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAC9C,eAAO,MAAM,oCAAoC,KAAK,CAAC;AAGvD,eAAO,MAAM,oBAAoB,SAAU,CAAC;AAE5C,eAAO,MAAM,0BAA0B,QAEtC,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,MAA0B,CAAC;AACzD,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAEvD,oBAAY,uBAAuB;IACjC,wBAAwB,6BAA6B;IACrD,WAAW,gBAAgB;IAC3B,mBAAmB,wBAAwB;CAC5C;AAED,eAAO,MAAM,eAAe,KAAsB,CAAC;AAEnD,eAAO,MAAM,sBAAsB,QAAiB,CAAC;AACrD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,mBAAmB,QAAY,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,6BAA6B,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/common/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAIhD,eAAO,MAAM,gCAAgC,aAI5C,CAAC;AAEF,eAAO,MAAM,6BAA6B,aAGzC,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAGjC,CAAC;AAEF,eAAO,MAAM,mBAAmB,OAAO,CAAC;AACxC,eAAO,MAAM,wBAAwB,QAAyB,CAAC;AAG/D,eAAO,MAAM,uBAAuB,QAAmB,CAAC;AACxD,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAC9C,eAAO,MAAM,oCAAoC,KAAK,CAAC;AAGvD,eAAO,MAAM,oBAAoB,SAAU,CAAC;AAE5C,eAAO,MAAM,0BAA0B,QAEtC,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,MAA0B,CAAC;AACzD,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAEvD,oBAAY,uBAAuB;IACjC,wBAAwB,6BAA6B;IACrD,WAAW,gBAAgB;IAC3B,mBAAmB,wBAAwB;CAC5C;AAED,eAAO,MAAM,eAAe,KAAsB,CAAC;AAEnD,eAAO,MAAM,sBAAsB,QAAiB,CAAC;AACrD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,mBAAmB,QAAY,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,6BAA6B,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UNBOUNDED_DATE_TIME_VALUE = exports.MEMORY_LOG_INTERVAL = exports.HARD_TIMEOUT_MULTIPLIER = exports.DEFAULT_LAMBDA_TIMEOUT = exports.LIBRARY_VERSION = exports.AirSyncDefaultItemTypes = exports.UNKNOWN_EVENT_TYPE = exports.SSOR_ATTACHMENT = exports.EVENT_SIZE_THRESHOLD_BYTES = exports.MAX_EVENT_SIZE_BYTES = exports.MAX_DEVREV_FILENAME_EXTENSION_LENGTH = exports.MAX_DEVREV_FILENAME_LENGTH = exports.MAX_DEVREV_ARTIFACT_SIZE = exports.ARTIFACT_BATCH_SIZE = exports.STATELESS_EVENT_TYPES = exports.STATELESS_LOADING_EVENT_TYPES = exports.STATELESS_EXTRACTION_EVENT_TYPES = void 0;
3
+ exports.UNBOUNDED_DATE_TIME_VALUE = exports.MEMORY_LOG_INTERVAL = exports.HARD_TIMEOUT_MULTIPLIER = exports.DEFAULT_LAMBDA_TIMEOUT = exports.LIBRARY_VERSION = exports.AirSyncDefaultItemTypes = exports.UNKNOWN_EVENT_TYPE = exports.SSOR_ATTACHMENT = exports.EVENT_SIZE_THRESHOLD_BYTES = exports.MAX_EVENT_SIZE_BYTES = exports.MAX_DEVREV_FILENAME_EXTENSION_LENGTH = exports.MAX_DEVREV_FILENAME_LENGTH = exports.S3_MULTIPART_CHUNK_SIZE = exports.MAX_DEVREV_ARTIFACT_SIZE = exports.ARTIFACT_BATCH_SIZE = exports.STATELESS_EVENT_TYPES = exports.STATELESS_LOADING_EVENT_TYPES = exports.STATELESS_EXTRACTION_EVENT_TYPES = void 0;
4
4
  const extraction_1 = require("../types/extraction");
5
5
  const helpers_1 = require("./helpers");
6
6
  exports.STATELESS_EXTRACTION_EVENT_TYPES = [
@@ -18,6 +18,9 @@ exports.STATELESS_EVENT_TYPES = [
18
18
  ];
19
19
  exports.ARTIFACT_BATCH_SIZE = 2000;
20
20
  exports.MAX_DEVREV_ARTIFACT_SIZE = 2 * 1024 * 1024 * 1024; // 2GB
21
+ // S3 multipart uploads require every non-final part to be at least 5 MiB. A
22
+ // 10 MiB part gives us a useful buffer while keeping the memory overhead low.
23
+ exports.S3_MULTIPART_CHUNK_SIZE = 10 * 1024 * 1024;
21
24
  exports.MAX_DEVREV_FILENAME_LENGTH = 256;
22
25
  exports.MAX_DEVREV_FILENAME_EXTENSION_LENGTH = 20;
23
26
  // Max SQS message size is 250KB, we want to leave some room for the other data in the message
@@ -1 +1 @@
1
- {"version":3,"file":"extraction-adapter.d.ts","sourceRoot":"","sources":["../../../src/multithreading/adapters/extraction-adapter.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EACL,oBAAoB,EACpB,aAAa,EACd,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAIL,kCAAkC,EAClC,yCAAyC,EAEzC,aAAa,EACb,2BAA2B,EAE5B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAkB,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,QAAQ,EAAkB,MAAM,oCAAoC,CAAC;AAE9E,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,2FAA2F;AAC3F,qBAAa,iBAAiB,CAC5B,cAAc,CACd,SAAQ,WAAW,CAAC,cAAc,CAAC;IACnC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,wBAAwB,CAAC,CAAkB;IACnD,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,qBAAqB,CAAC,CAAS;IACvC,OAAO,CAAC,sBAAsB,CAAa;IAY3C,6EAA6E;IAC7E,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAOxC,eAAe,CAAC,KAAK,EAAE,aAAa,EAAE;IAatC,OAAO,CAAC,UAAU;IAoDlB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAW3C,gEAAgE;IAChE,IAAI,SAAS,IAAI,SAAS,QAAQ,EAAE,CAEnC;IAED,OAAO,CAAC,YAAY;IAMpB;;;OAGG;IACH,IAAI,uBAAuB,IAAI,aAAa,EAAE,GAAG,SAAS,CAEzD;IAED,IAAI,uBAAuB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,SAAS,EAE9D;IA6HD,OAAO,CAAC,kBAAkB;YAaZ,cAAc;IAmB5B;;;;OAIG;IACG,iBAAiB,CACrB,UAAU,EAAE,oBAAoB,EAChC,MAAM,EAAE,yCAAyC,GAChD,OAAO,CAAC,2BAA2B,CAAC;IAwGjC,iBAAiB,CAAC,QAAQ,EAAE,EAChC,MAAM,EACN,UAAU,EACV,SAAa,GACd,EAAE;QACD,MAAM,EAAE,yCAAyC,CAAC;QAClD,UAAU,CAAC,EAAE,kCAAkC,CAC7C,cAAc,EACd,oBAAoB,EAAE,EACtB,QAAQ,CACT,CAAC;QACF,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,UAAU,CAAC;CAsHxB"}
1
+ {"version":3,"file":"extraction-adapter.d.ts","sourceRoot":"","sources":["../../../src/multithreading/adapters/extraction-adapter.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EACL,oBAAoB,EACpB,aAAa,EACd,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAIL,kCAAkC,EAClC,yCAAyC,EAEzC,aAAa,EACb,2BAA2B,EAE5B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAkB,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,QAAQ,EAAkB,MAAM,oCAAoC,CAAC;AAE9E,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,2FAA2F;AAC3F,qBAAa,iBAAiB,CAC5B,cAAc,CACd,SAAQ,WAAW,CAAC,cAAc,CAAC;IACnC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,wBAAwB,CAAC,CAAkB;IACnD,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,qBAAqB,CAAC,CAAS;IACvC,OAAO,CAAC,sBAAsB,CAAa;IAY3C,6EAA6E;IAC7E,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAOxC,eAAe,CAAC,KAAK,EAAE,aAAa,EAAE;IAatC,OAAO,CAAC,UAAU;IAoDlB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAW3C,gEAAgE;IAChE,IAAI,SAAS,IAAI,SAAS,QAAQ,EAAE,CAEnC;IAED,OAAO,CAAC,YAAY;IAMpB;;;OAGG;IACH,IAAI,uBAAuB,IAAI,aAAa,EAAE,GAAG,SAAS,CAEzD;IAED,IAAI,uBAAuB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,SAAS,EAE9D;IA6HD,OAAO,CAAC,kBAAkB;YAaZ,cAAc;IAmB5B;;;;OAIG;IACG,iBAAiB,CACrB,UAAU,EAAE,oBAAoB,EAChC,MAAM,EAAE,yCAAyC,GAChD,OAAO,CAAC,2BAA2B,CAAC;IA4IjC,iBAAiB,CAAC,QAAQ,EAAE,EAChC,MAAM,EACN,UAAU,EACV,SAAa,GACd,EAAE;QACD,MAAM,EAAE,yCAAyC,CAAC;QAClD,UAAU,CAAC,EAAE,kCAAkC,CAC7C,cAAc,EACd,oBAAoB,EAAE,EACtB,QAAQ,CACT,CAAC;QACF,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,UAAU,CAAC;CAsHxB"}
@@ -230,44 +230,70 @@ class ExtractionAdapter extends base_adapter_1.BaseAdapter {
230
230
  ((_a = httpStream.headers['content-type']) === null || _a === void 0 ? void 0 : _a.toString()) ||
231
231
  'application/octet-stream';
232
232
  const contentLength = (_b = httpStream.headers['content-length']) === null || _b === void 0 ? void 0 : _b.toString();
233
- const fileSize = contentLength ? parseInt(contentLength) : undefined;
234
- const { error: artifactUrlError, response: artifactUrlResponse } = await this.uploader.getArtifactUploadUrl(attachment.file_name, fileType, fileSize);
235
- if (artifactUrlError) {
236
- (0, uploader_helpers_1.destroyHttpStream)(httpStream);
237
- return {
238
- status: 'error',
239
- error: {
240
- message: `Error while preparing artifact for attachment ID ${attachment.id}. Skipping attachment. ${(0, logger_1.serializeError)(artifactUrlError)}`,
241
- fileSize,
242
- },
243
- };
244
- }
245
- const { error: uploadedArtifactError } = await this.uploader.streamArtifact(artifactUrlResponse, httpStream);
246
- if (uploadedArtifactError) {
247
- (0, uploader_helpers_1.destroyHttpStream)(httpStream);
248
- return {
249
- status: 'error',
250
- error: {
251
- message: `Error while streaming to artifact for attachment ID ${attachment.id}. Skipping attachment. ` +
252
- (0, logger_1.serializeError)(uploadedArtifactError),
253
- fileSize,
254
- },
255
- };
233
+ const parsedFileSize = contentLength
234
+ ? Number.parseInt(contentLength, 10)
235
+ : undefined;
236
+ const fileSize = parsedFileSize !== undefined && Number.isFinite(parsedFileSize)
237
+ ? parsedFileSize
238
+ : undefined;
239
+ let artifactId;
240
+ if (fileSize === undefined) {
241
+ // Unknown-length streams cannot be uploaded using a presigned POST with
242
+ // a guessed Content-Length. Stream them through S3 multipart upload.
243
+ const { response: multipartUpload, error: multipartUploadError } = await this.uploader.streamArtifactMultipart(attachment.file_name, fileType, httpStream);
244
+ if (multipartUploadError || !multipartUpload) {
245
+ return {
246
+ status: 'error',
247
+ error: {
248
+ message: `Error while uploading attachment for attachment ID ${attachment.id}. Skipping attachment. ` +
249
+ (0, logger_1.serializeError)(multipartUploadError !== null && multipartUploadError !== void 0 ? multipartUploadError : new Error('Multipart upload returned no response.')),
250
+ fileSize,
251
+ },
252
+ };
253
+ }
254
+ artifactId = multipartUpload.artifact_id;
256
255
  }
257
- const { error: confirmArtifactUploadError } = await this.uploader.confirmArtifactUpload(artifactUrlResponse.artifact_id);
258
- if (confirmArtifactUploadError) {
259
- return {
260
- status: 'error',
261
- error: {
262
- message: `Error while confirming upload for attachment ID ${attachment.id}. ` +
263
- (0, logger_1.serializeError)(confirmArtifactUploadError),
264
- fileSize,
265
- },
266
- };
256
+ else {
257
+ // Known-size attachments retain the existing single-request upload flow.
258
+ const { error: artifactUrlError, response: artifactUrlResponse } = await this.uploader.getArtifactUploadUrl(attachment.file_name, fileType, fileSize);
259
+ if (artifactUrlError || !artifactUrlResponse) {
260
+ (0, uploader_helpers_1.destroyHttpStream)(httpStream);
261
+ return {
262
+ status: 'error',
263
+ error: {
264
+ message: `Error while preparing artifact for attachment ID ${attachment.id}. Skipping attachment. ${(0, logger_1.serializeError)(artifactUrlError !== null && artifactUrlError !== void 0 ? artifactUrlError : new Error('No artifact upload response.'))}`,
265
+ fileSize,
266
+ },
267
+ };
268
+ }
269
+ const { error: uploadedArtifactError } = await this.uploader.streamArtifact(artifactUrlResponse, httpStream);
270
+ if (uploadedArtifactError) {
271
+ (0, uploader_helpers_1.destroyHttpStream)(httpStream);
272
+ return {
273
+ status: 'error',
274
+ error: {
275
+ message: `Error while streaming to artifact for attachment ID ${attachment.id}. Skipping attachment. ` +
276
+ (0, logger_1.serializeError)(uploadedArtifactError),
277
+ fileSize,
278
+ },
279
+ };
280
+ }
281
+ const { error: confirmArtifactUploadError } = await this.uploader.confirmArtifactUpload(artifactUrlResponse.artifact_id);
282
+ if (confirmArtifactUploadError) {
283
+ return {
284
+ status: 'error',
285
+ error: {
286
+ message: `Error while confirming upload for attachment ID ${attachment.id}. ` +
287
+ (0, logger_1.serializeError)(confirmArtifactUploadError),
288
+ fileSize,
289
+ },
290
+ };
291
+ }
292
+ artifactId = artifactUrlResponse.artifact_id;
267
293
  }
268
294
  const ssorAttachment = {
269
295
  id: {
270
- devrev: artifactUrlResponse.artifact_id,
296
+ devrev: artifactId,
271
297
  external: attachment.id,
272
298
  },
273
299
  parent_id: {
@@ -1,7 +1,7 @@
1
1
  import { AxiosResponse } from 'axios';
2
2
  import { NormalizedAttachment } from '../repo/repo.interfaces';
3
3
  import { HttpStreamResponse } from '../types/extraction';
4
- import { ArtifactToUpload, UploaderFactoryInterface, UploaderResult, UploadResponse } from './uploader.interfaces';
4
+ import { ArtifactToUpload, MultipartUpload, UploaderFactoryInterface, UploaderResult, UploadResponse } from './uploader.interfaces';
5
5
  export declare class Uploader {
6
6
  private isLocalDevelopment?;
7
7
  private devrevApiEndpoint;
@@ -17,6 +17,26 @@ export declare class Uploader {
17
17
  uploadArtifact(artifact: ArtifactToUpload, file: Buffer): Promise<UploaderResult<AxiosResponse>>;
18
18
  /** Streams a file from an HTTP response directly to the artifact's upload URL. */
19
19
  streamArtifact(artifact: ArtifactToUpload, fileStream: HttpStreamResponse): Promise<UploaderResult<AxiosResponse>>;
20
+ /**
21
+ * Creates an S3 multipart upload for an artifact whose size is not known.
22
+ * The returned URL is for part 1; subsequent URLs are obtained with
23
+ * getNextMultipartUploadPart.
24
+ */
25
+ createMultipartUpload(filename: string, fileType: string, partSize?: number): Promise<UploaderResult<MultipartUpload>>;
26
+ /** Gets a presigned URL for the next multipart upload part. */
27
+ getNextMultipartUploadPart(artifactId: string, cursor: string, partSize?: number): Promise<UploaderResult<MultipartUpload>>;
28
+ /** Finalizes and publishes an S3 multipart artifact upload. */
29
+ completeMultipartUpload(artifactId: string, cursor: string): Promise<UploaderResult<AxiosResponse>>;
30
+ /**
31
+ * Streams an attachment of unknown size through an S3 multipart upload.
32
+ * At most one 10 MiB part is buffered at a time. The final part may be
33
+ * smaller than S3's 5 MiB minimum.
34
+ */
35
+ streamArtifactMultipart(filename: string, fileType: string, fileStream: HttpStreamResponse): Promise<UploaderResult<{
36
+ artifact_id: string;
37
+ }>>;
38
+ /** Uploads one raw part to its presigned S3 URL. */
39
+ private uploadMultipartPart;
20
40
  confirmArtifactUpload(artifactId: string): Promise<{
21
41
  response?: AxiosResponse;
22
42
  error?: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../../src/uploader/uploader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAOtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAWzD,OAAO,EAEL,gBAAgB,EAChB,wBAAwB,EACxB,cAAc,EACd,cAAc,EACf,MAAM,uBAAuB,CAAC;AAE/B,qBAAa,QAAQ;IACnB,OAAO,CAAC,kBAAkB,CAAC,CAAU;IACrC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,gBAAgB,CAAU;gBAEtB,EAAE,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,wBAAwB;IAW1E,mFAAmF;IAC7E,MAAM,CACV,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM,GAChC,OAAO,CAAC,cAAc,CAAC;IAwEpB,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IA2B5C,mFAAmF;IAC7E,cAAc,CAClB,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAmBzC,kFAAkF;IAC5E,cAAc,CAClB,QAAQ,EAAE,gBAAgB,EAC1B,UAAU,EAAE,kBAAkB,GAC7B,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAqCnC,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QACvD,QAAQ,CAAC,EAAE,aAAa,CAAC;QACzB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,CAAC;IAgCF,oEAAoE;IAC9D,4BAA4B,CAAC,EACjC,QAAQ,GACT,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC;QACV,WAAW,CAAC,EAAE,oBAAoB,EAAE,CAAC;QACrC,KAAK,CAAC,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;KAC7B,CAAC;YAuDY,sBAAsB;YAsBtB,gBAAgB;IAc9B,yEAAyE;IACnE,yBAAyB,CAAC,EAC9B,UAAU,EACV,SAAiB,GAClB,EAAE;QACD,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC;CA6B/C"}
1
+ {"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../../src/uploader/uploader.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAUtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAWzD,OAAO,EAEL,gBAAgB,EAChB,eAAe,EACf,wBAAwB,EACxB,cAAc,EACd,cAAc,EACf,MAAM,uBAAuB,CAAC;AAE/B,qBAAa,QAAQ;IACnB,OAAO,CAAC,kBAAkB,CAAC,CAAU;IACrC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,gBAAgB,CAAU;gBAEtB,EAAE,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,wBAAwB;IAW1E,mFAAmF;IAC7E,MAAM,CACV,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM,GAChC,OAAO,CAAC,cAAc,CAAC;IAwEpB,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IA2B5C,mFAAmF;IAC7E,cAAc,CAClB,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAmBzC,kFAAkF;IAC5E,cAAc,CAClB,QAAQ,EAAE,gBAAgB,EAC1B,UAAU,EAAE,kBAAkB,GAC7B,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAqCzC;;;;OAIG;IACG,qBAAqB,CACzB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,SAA0B,GACjC,OAAO,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC;IAkB3C,+DAA+D;IACzD,0BAA0B,CAC9B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,QAAQ,SAA0B,GACjC,OAAO,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC;IAkB3C,+DAA+D;IACzD,uBAAuB,CAC3B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAoBzC;;;;OAIG;IACG,uBAAuB,CAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,kBAAkB,GAC7B,OAAO,CAAC,cAAc,CAAC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IA6HnD,oDAAoD;YACtC,mBAAmB;IAsD3B,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QACvD,QAAQ,CAAC,EAAE,aAAa,CAAC;QACzB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,CAAC;IAgCF,oEAAoE;IAC9D,4BAA4B,CAAC,EACjC,QAAQ,GACT,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC;QACV,WAAW,CAAC,EAAE,oBAAoB,EAAE,CAAC;QACrC,KAAK,CAAC,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;KAC7B,CAAC;YAuDY,sBAAsB;YAsBtB,gBAAgB;IAc9B,yEAAyE;IACnE,yBAAyB,CAAC,EAC9B,UAAU,EACV,SAAiB,GAClB,EAAE;QACD,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC;CA6B/C"}
@@ -40,6 +40,12 @@ export interface ArtifactToUpload {
40
40
  value: string;
41
41
  }[];
42
42
  }
43
+ /** Multipart upload state returned by the artifacts upload endpoints. */
44
+ export interface MultipartUpload {
45
+ id: string;
46
+ url: string;
47
+ cursor: string;
48
+ }
43
49
  export interface UploadResponse {
44
50
  artifact?: Artifact;
45
51
  error?: ErrorRecord;
@@ -1 +1 @@
1
- {"version":3,"file":"uploader.interfaces.d.ts","sourceRoot":"","sources":["../../src/uploader/uploader.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,uFAAuF;IACvF,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,IACxB;IAAE,QAAQ,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzC,oBAAY,iBAAiB;IAC3B,iBAAiB,wBAAwB;IACzC,iBAAiB,wBAAwB;IACzC,kBAAkB,yBAAyB;IAC3C,kBAAkB,yBAAyB;CAC5C;AAED,iEAAiE;AACjE,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC,EAAE,MAAM,CAAC;IAC/C,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC,EAAE,MAAM,CAAC;IAC/C,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC,EAAE,MAAM,CAAC;IAChD,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC,EAAE,MAAM,CAAC;CACjD;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,QAAQ,EACN,iBAAiB,CAAC,iBAAiB,GACnC,iBAAiB,CAAC,iBAAiB,GACnC,iBAAiB,CAAC,kBAAkB,GACpC,iBAAiB,CAAC,kBAAkB,CACvC,CAAC;AAEF,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;CACL;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE;QACF,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,SAAS,EAAE;QACT,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB"}
1
+ {"version":3,"file":"uploader.interfaces.d.ts","sourceRoot":"","sources":["../../src/uploader/uploader.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,uFAAuF;IACvF,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,IACxB;IAAE,QAAQ,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzC,oBAAY,iBAAiB;IAC3B,iBAAiB,wBAAwB;IACzC,iBAAiB,wBAAwB;IACzC,kBAAkB,yBAAyB;IAC3C,kBAAkB,yBAAyB;CAC5C;AAED,iEAAiE;AACjE,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC,EAAE,MAAM,CAAC;IAC/C,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC,EAAE,MAAM,CAAC;IAC/C,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC,EAAE,MAAM,CAAC;IAChD,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC,EAAE,MAAM,CAAC;CACjD;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,QAAQ,EACN,iBAAiB,CAAC,iBAAiB,GACnC,iBAAiB,CAAC,iBAAiB,GACnC,iBAAiB,CAAC,kBAAkB,GACpC,iBAAiB,CAAC,kBAAkB,CACvC,CAAC;AAEF,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;CACL;AAED,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE;QACF,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,SAAS,EAAE;QACT,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB"}
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Uploader = void 0;
7
+ const http_1 = __importDefault(require("http"));
8
+ const https_1 = __importDefault(require("https"));
7
9
  const form_data_1 = __importDefault(require("form-data"));
8
10
  const js_jsonl_1 = require("js-jsonl");
9
11
  const constants_1 = require("../common/constants");
@@ -136,6 +138,201 @@ class Uploader {
136
138
  return { error };
137
139
  }
138
140
  }
141
+ /**
142
+ * Creates an S3 multipart upload for an artifact whose size is not known.
143
+ * The returned URL is for part 1; subsequent URLs are obtained with
144
+ * getNextMultipartUploadPart.
145
+ */
146
+ async createMultipartUpload(filename, fileType, partSize = constants_1.S3_MULTIPART_CHUNK_SIZE) {
147
+ const url = `${this.devrevApiEndpoint}/internal/artifacts.upload.create`;
148
+ try {
149
+ const response = await client_1.axiosClient.post(url, {
150
+ file_name: (0, uploader_helpers_1.truncateFilename)(filename),
151
+ file_type: fileType,
152
+ part_size: partSize,
153
+ }, { headers: Object.assign({}, this.defaultHeaders) });
154
+ return { response: response.data };
155
+ }
156
+ catch (error) {
157
+ return { error };
158
+ }
159
+ }
160
+ /** Gets a presigned URL for the next multipart upload part. */
161
+ async getNextMultipartUploadPart(artifactId, cursor, partSize = constants_1.S3_MULTIPART_CHUNK_SIZE) {
162
+ const url = `${this.devrevApiEndpoint}/internal/artifacts.upload.next`;
163
+ try {
164
+ const response = await client_1.axiosClient.post(url, {
165
+ id: artifactId,
166
+ cursor,
167
+ part_size: partSize,
168
+ }, { headers: Object.assign({}, this.defaultHeaders) });
169
+ return { response: response.data };
170
+ }
171
+ catch (error) {
172
+ return { error };
173
+ }
174
+ }
175
+ /** Finalizes and publishes an S3 multipart artifact upload. */
176
+ async completeMultipartUpload(artifactId, cursor) {
177
+ const url = `${this.devrevApiEndpoint}/internal/artifacts.upload.complete`;
178
+ try {
179
+ const response = await client_1.axiosClient.post(url, {
180
+ id: artifactId,
181
+ cursor,
182
+ }, { headers: Object.assign({}, this.defaultHeaders) });
183
+ if (response.status >= 200 && response.status < 300) {
184
+ return { response };
185
+ }
186
+ return { error: response };
187
+ }
188
+ catch (error) {
189
+ return { error };
190
+ }
191
+ }
192
+ /**
193
+ * Streams an attachment of unknown size through an S3 multipart upload.
194
+ * At most one 10 MiB part is buffered at a time. The final part may be
195
+ * smaller than S3's 5 MiB minimum.
196
+ */
197
+ async streamArtifactMultipart(filename, fileType, fileStream) {
198
+ try {
199
+ const iterator = fileStream.data[Symbol.asyncIterator]();
200
+ let pendingChunk;
201
+ // Read a complete part before requesting its presigned URL. The gateway
202
+ // signs Content-Length using part_size, so the requested part size must
203
+ // be the exact byte length that will be sent (including the final part).
204
+ const readPart = async () => {
205
+ const chunks = [];
206
+ let length = 0;
207
+ while (length < constants_1.S3_MULTIPART_CHUNK_SIZE) {
208
+ let chunk = pendingChunk;
209
+ pendingChunk = undefined;
210
+ if (!chunk) {
211
+ const next = await iterator.next();
212
+ if (next.done) {
213
+ break;
214
+ }
215
+ chunk = Buffer.isBuffer(next.value)
216
+ ? next.value
217
+ : Buffer.from(next.value);
218
+ }
219
+ if (chunk.length === 0) {
220
+ continue;
221
+ }
222
+ const capacity = constants_1.S3_MULTIPART_CHUNK_SIZE - length;
223
+ if (chunk.length <= capacity) {
224
+ chunks.push(chunk);
225
+ length += chunk.length;
226
+ }
227
+ else {
228
+ chunks.push(chunk.subarray(0, capacity));
229
+ pendingChunk = chunk.subarray(capacity);
230
+ length += capacity;
231
+ }
232
+ }
233
+ return length > 0 ? Buffer.concat(chunks, length) : undefined;
234
+ };
235
+ let part = await readPart();
236
+ if (!part) {
237
+ throw new Error('Cannot complete a multipart upload with an empty file.');
238
+ }
239
+ const { response: createdUpload, error: createError } = await this.createMultipartUpload(filename, fileType, part.length);
240
+ if (createError || !createdUpload) {
241
+ return {
242
+ error: createError !== null && createError !== void 0 ? createError : new Error('Multipart upload creation returned no response.'),
243
+ };
244
+ }
245
+ const artifactId = createdUpload.id;
246
+ let cursor = createdUpload.cursor;
247
+ let uploadUrl = createdUpload.url;
248
+ if (!artifactId || !cursor || !uploadUrl) {
249
+ return {
250
+ error: new Error('Multipart upload creation returned incomplete upload information.'),
251
+ };
252
+ }
253
+ while (part) {
254
+ const { response, error } = await this.uploadMultipartPart(uploadUrl, part, part.length);
255
+ if (error || !response) {
256
+ return {
257
+ error: error !== null && error !== void 0 ? error : new Error('Multipart part upload returned no response.'),
258
+ };
259
+ }
260
+ const nextPart = part.length === constants_1.S3_MULTIPART_CHUNK_SIZE
261
+ ? await readPart()
262
+ : undefined;
263
+ if (!nextPart) {
264
+ break;
265
+ }
266
+ const { response: nextUpload, error: nextError } = await this.getNextMultipartUploadPart(artifactId, cursor, nextPart.length);
267
+ if (nextError || !(nextUpload === null || nextUpload === void 0 ? void 0 : nextUpload.url) || !nextUpload.cursor) {
268
+ return {
269
+ error: nextError !== null && nextError !== void 0 ? nextError : new Error('Could not obtain the next multipart upload URL.'),
270
+ };
271
+ }
272
+ part = nextPart;
273
+ uploadUrl = nextUpload.url;
274
+ cursor = nextUpload.cursor;
275
+ }
276
+ const { error: completeError } = await this.completeMultipartUpload(artifactId, cursor);
277
+ if (completeError) {
278
+ return { error: completeError };
279
+ }
280
+ return { response: { artifact_id: artifactId } };
281
+ }
282
+ catch (error) {
283
+ return { error };
284
+ }
285
+ finally {
286
+ (0, uploader_helpers_1.destroyHttpStream)(fileStream);
287
+ }
288
+ }
289
+ /** Uploads one raw part to its presigned S3 URL. */
290
+ async uploadMultipartPart(uploadUrl, part, contentLength) {
291
+ try {
292
+ // Use Node's native request instead of Axios for the presigned request.
293
+ // Axios may normalize, remove, or recalculate Content-Length; that is
294
+ // not allowed when the presigned URL includes this header in its
295
+ // signature.
296
+ const parsedUrl = new URL(uploadUrl);
297
+ const transport = parsedUrl.protocol === 'https:' ? https_1.default : http_1.default;
298
+ const response = await new Promise((resolve, reject) => {
299
+ const request = transport.request(parsedUrl, {
300
+ method: 'PUT',
301
+ headers: {
302
+ 'Content-Length': String(contentLength),
303
+ },
304
+ }, (incomingResponse) => {
305
+ const chunks = [];
306
+ incomingResponse.on('data', (chunk) => chunks.push(chunk));
307
+ incomingResponse.on('end', () => {
308
+ var _a, _b;
309
+ const data = Buffer.concat(chunks);
310
+ const status = (_a = incomingResponse.statusCode) !== null && _a !== void 0 ? _a : 0;
311
+ const axiosResponse = {
312
+ data,
313
+ status,
314
+ statusText: (_b = incomingResponse.statusMessage) !== null && _b !== void 0 ? _b : '',
315
+ headers: incomingResponse.headers,
316
+ config: {},
317
+ request,
318
+ };
319
+ if (status >= 200 && status < 400) {
320
+ resolve(axiosResponse);
321
+ }
322
+ else {
323
+ reject(new Error(`Multipart part upload returned HTTP ${status}.`));
324
+ }
325
+ });
326
+ });
327
+ request.once('error', reject);
328
+ request.end(part);
329
+ });
330
+ return { response };
331
+ }
332
+ catch (error) {
333
+ return { error };
334
+ }
335
+ }
139
336
  async confirmArtifactUpload(artifactId) {
140
337
  const url = `${this.devrevApiEndpoint}/internal/airdrop.artifacts.confirm-upload`;
141
338
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devrev/airsync-sdk",
3
- "version": "2.0.0",
3
+ "version": "2.1.0-beta.0",
4
4
  "description": "Typescript SDK for building AirSync snap-ins on the DevRev platform.",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",