@bytescale/sdk 1.0.0-alpha.1

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.
@@ -0,0 +1,178 @@
1
+ import { ErrorResponse } from "./models";
2
+ /**
3
+ * @bytescale/bytescale-api
4
+ * Bytescale API
5
+ *
6
+ * The version of the OpenAPI document: 2.0.0
7
+ * Contact: hello@bytescale.com
8
+ *
9
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
10
+ * https://openapi-generator.tech
11
+ * Do not edit the class manually.
12
+ */
13
+ export declare const BASE_PATH: string;
14
+ export interface ConfigurationParameters {
15
+ basePath?: string;
16
+ fetchApi?: FetchAPI;
17
+ middleware?: Middleware[];
18
+ queryParamsStringify?: (params: HTTPQuery) => string;
19
+ username?: string;
20
+ password?: string;
21
+ apiKey?: string | ((name: string) => string);
22
+ accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string | Promise<string>);
23
+ headers?: HTTPHeaders;
24
+ credentials?: RequestCredentials;
25
+ }
26
+ export declare class Configuration {
27
+ private configuration;
28
+ constructor(configuration?: ConfigurationParameters);
29
+ set config(configuration: Configuration);
30
+ get basePath(): string;
31
+ get fetchApi(): FetchAPI | undefined;
32
+ get middleware(): Middleware[];
33
+ get queryParamsStringify(): (params: HTTPQuery) => string;
34
+ get username(): string | undefined;
35
+ get password(): string | undefined;
36
+ get apiKey(): ((name: string) => string) | undefined;
37
+ get accessToken(): ((name?: string, scopes?: string[]) => string | Promise<string>) | undefined;
38
+ get headers(): HTTPHeaders | undefined;
39
+ get credentials(): RequestCredentials | undefined;
40
+ }
41
+ export declare const DefaultConfig: Configuration;
42
+ /**
43
+ * This is the base class for all generated API classes.
44
+ */
45
+ export declare class BaseAPI {
46
+ protected configuration: Configuration;
47
+ private middleware;
48
+ constructor(configuration?: Configuration);
49
+ withMiddleware<T extends BaseAPI>(this: T, ...middlewares: Middleware[]): T;
50
+ withPreMiddleware<T extends BaseAPI>(this: T, ...preMiddlewares: Array<Middleware["pre"]>): T;
51
+ withPostMiddleware<T extends BaseAPI>(this: T, ...postMiddlewares: Array<Middleware["post"]>): T;
52
+ protected request(context: RequestOpts, initOverrides: RequestInit | InitOverrideFunction | undefined, operationBasePathOverride: string | undefined): Promise<Response>;
53
+ private createFetchParams;
54
+ private fetchApi;
55
+ /**
56
+ * Create a shallow clone of `this` by constructing a new instance
57
+ * and then shallow cloning data members.
58
+ */
59
+ private clone;
60
+ }
61
+ export declare class UploadApiError extends Error {
62
+ name: "UploadApiError";
63
+ readonly errorCode: string;
64
+ readonly details: any | undefined;
65
+ constructor(response: ErrorResponse);
66
+ }
67
+ export declare class ResponseError extends Error {
68
+ response: Response;
69
+ name: "ResponseError";
70
+ constructor(response: Response, msg?: string);
71
+ }
72
+ export declare class FetchError extends Error {
73
+ cause: Error;
74
+ name: "FetchError";
75
+ constructor(cause: Error, msg?: string);
76
+ }
77
+ export declare class RequiredError extends Error {
78
+ field: string;
79
+ name: "RequiredError";
80
+ constructor(field: string, msg?: string);
81
+ }
82
+ export declare const COLLECTION_FORMATS: {
83
+ csv: string;
84
+ ssv: string;
85
+ tsv: string;
86
+ pipes: string;
87
+ };
88
+ export declare type FetchAPI = WindowOrWorkerGlobalScope["fetch"];
89
+ export declare type Json = any;
90
+ export declare type HTTPMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
91
+ export declare type HTTPHeaders = {
92
+ [key: string]: string;
93
+ };
94
+ export declare type HTTPQuery = {
95
+ [key: string]: string | number | null | boolean | Array<string | number | null | boolean> | Set<string | number | null | boolean> | HTTPQuery;
96
+ };
97
+ export declare type HTTPBody = Json | FormData | URLSearchParams;
98
+ export declare type HTTPRequestInit = {
99
+ headers?: HTTPHeaders;
100
+ method: HTTPMethod;
101
+ credentials?: RequestCredentials;
102
+ body?: HTTPBody;
103
+ };
104
+ export declare type ModelPropertyNaming = "camelCase" | "snake_case" | "PascalCase" | "original";
105
+ export declare type InitOverrideFunction = (requestContext: {
106
+ init: HTTPRequestInit;
107
+ context: RequestOpts;
108
+ }) => Promise<RequestInit>;
109
+ export interface FetchParams {
110
+ url: string;
111
+ init: RequestInit;
112
+ }
113
+ export interface RequestOpts {
114
+ path: string;
115
+ method: HTTPMethod;
116
+ headers: HTTPHeaders;
117
+ query?: HTTPQuery;
118
+ body?: HTTPBody;
119
+ }
120
+ export declare function querystring(params: HTTPQuery, prefix?: string): string;
121
+ export declare function canConsumeForm(consumes: Consume[]): boolean;
122
+ export interface Consume {
123
+ contentType: string;
124
+ }
125
+ export interface RequestContext {
126
+ fetch: FetchAPI;
127
+ url: string;
128
+ init: RequestInit;
129
+ }
130
+ export interface ResponseContext {
131
+ fetch: FetchAPI;
132
+ url: string;
133
+ init: RequestInit;
134
+ response: Response;
135
+ }
136
+ export interface ErrorContext {
137
+ fetch: FetchAPI;
138
+ url: string;
139
+ init: RequestInit;
140
+ error: unknown;
141
+ response?: Response;
142
+ }
143
+ export interface Middleware {
144
+ pre?(context: RequestContext): Promise<FetchParams | void>;
145
+ post?(context: ResponseContext): Promise<Response | void>;
146
+ onError?(context: ErrorContext): Promise<Response | void>;
147
+ }
148
+ export interface ApiResponse<T> {
149
+ raw: Response;
150
+ value(): Promise<T>;
151
+ }
152
+ export interface ResponseTransformer<T> {
153
+ (json: any): T;
154
+ }
155
+ export declare class JSONApiResponse<T> {
156
+ raw: Response;
157
+ private transformer;
158
+ constructor(raw: Response, transformer?: ResponseTransformer<T>);
159
+ value(): Promise<T>;
160
+ }
161
+ export declare class VoidApiResponse {
162
+ raw: Response;
163
+ constructor(raw: Response);
164
+ value(): Promise<void>;
165
+ }
166
+ export declare class BinaryResult {
167
+ raw: Response;
168
+ constructor(raw: Response);
169
+ stream(): ReadableStream<Uint8Array>;
170
+ text(): Promise<string>;
171
+ blob(): Promise<Blob>;
172
+ json(): Promise<any>;
173
+ }
174
+ export declare class TextApiResponse {
175
+ raw: Response;
176
+ constructor(raw: Response);
177
+ value(): Promise<string>;
178
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./gen";
2
+ export * from "./utils";
@@ -0,0 +1,29 @@
1
+ /// <reference types="node" />
2
+ export declare class ChunkedStream {
3
+ private readonly source;
4
+ private buffer;
5
+ private consumer;
6
+ private isSourceFullyConsumed;
7
+ private isFinishedConsuming;
8
+ private resolver;
9
+ constructor(source: NodeJS.ReadableStream);
10
+ /**
11
+ * If the source stream is larger than the 'size' the user is consuming (i.e. they're only wanting to upload a subset
12
+ * of the stream) then the stream won't be resolved by the 'end' event inside 'runChunkPipeline', so calling this
13
+ * method is necessary.
14
+ */
15
+ finishedConsuming(): void;
16
+ /**
17
+ * Promise resolves when the entire stream has finished processing, or an error occurs.
18
+ * You must call 'take' a sufficient number of times after calling this method in order for this promise to resolve.
19
+ */
20
+ runChunkPipeline(): Promise<void>;
21
+ /**
22
+ * Only call 'take' after the previously returned stream has been fully consumed.
23
+ */
24
+ take(bytes: number): Promise<NodeJS.ReadableStream>;
25
+ private consumeFromBuffer;
26
+ private splitBuffer;
27
+ private emptyStream;
28
+ private finishStream;
29
+ }
@@ -0,0 +1,35 @@
1
+ /// <reference types="node" />
2
+ /**
3
+ * Supported implementations:
4
+ * - Node.js 'buffer.Blob', e.g. 'new buffer.Blob([JSON.stringify({someValue: 42})], {type: "application/json"})'
5
+ * - Browser 'Blob', e.g. 'new Blob([JSON.stringify({someValue: 42})], {type: "application/json"})'
6
+ * - Browser 'File', e.g. from a <input type="file" onchange="...">
7
+ */
8
+ import { BeginMultipartUploadRequest } from "../gen";
9
+ import { ChunkedStream } from "./ChunkedStream";
10
+ export interface BlobLike {
11
+ readonly name?: string;
12
+ readonly size: number;
13
+ readonly type?: string;
14
+ slice: (start?: number, end?: number) => BlobLike;
15
+ }
16
+ export interface CancellationToken {
17
+ isCancelled: boolean;
18
+ }
19
+ export declare class CancelledError extends Error {
20
+ msg?: string | undefined;
21
+ name: "CancelledError";
22
+ constructor(msg?: string | undefined);
23
+ }
24
+ export declare type UploadSource = NodeJS.ReadableStream | BlobLike | Buffer | string;
25
+ export interface UploadManagerParams extends Omit<BeginMultipartUploadRequest, "size" | "protocol"> {
26
+ accountId: string;
27
+ cancellationToken?: CancellationToken;
28
+ data: UploadSource;
29
+ maxConcurrentUploadParts?: number;
30
+ /**
31
+ * Only required if 'data' is a 'ReadableStream'.
32
+ */
33
+ size?: number;
34
+ }
35
+ export declare type UploadSourceProcessed = ChunkedStream | BlobLike | Buffer;
@@ -0,0 +1,13 @@
1
+ /// <reference types="node" />
2
+ import type * as stream from "stream";
3
+ import type * as buffer from "buffer";
4
+ import { BlobLike } from "./Model";
5
+ /**
6
+ * Provides access to Node.js built-in libraries. Should only be called when we're certain we're running in Node.js.
7
+ *
8
+ * We import lazily to support browsers, which don't have these modules, but also won't call these methods so won't trigger the imports.
9
+ */
10
+ export declare class NodeUtils {
11
+ static createStream(): Promise<stream.Readable>;
12
+ static createBlob(sources: string[], options?: buffer.BlobOptions): Promise<BlobLike>;
13
+ }
@@ -0,0 +1,39 @@
1
+ import { FileDetails } from "../gen";
2
+ import { UploadManagerParams } from "./Model";
3
+ export declare class UploadManager {
4
+ private readonly configuration;
5
+ private readonly stringMimeType;
6
+ private readonly defaultMaxConcurrentUploadParts;
7
+ private readonly uploadApi;
8
+ constructor(configuration?: import("../gen").Configuration);
9
+ upload(request: UploadManagerParams): Promise<FileDetails>;
10
+ private checkIfCancelled;
11
+ private beginUpload;
12
+ private uploadPart;
13
+ /**
14
+ * Returns etag for the part.
15
+ */
16
+ private putUploadPart;
17
+ private coerceRequestBody;
18
+ private makeRequestBody;
19
+ private bufferToStream;
20
+ /**
21
+ * Only expected to be called in Node.js environments, and as such, we can assume 'BlobLike' is not a DOM 'File' object.
22
+ */
23
+ private blobToBuffer;
24
+ private sliceDataForRequest;
25
+ private isNodeJs;
26
+ private getUploadPart;
27
+ private calculateSize;
28
+ private calculateMime;
29
+ private calculateOriginalFileName;
30
+ private calculateMaxConcurrency;
31
+ private getChunkedStream;
32
+ private foldData;
33
+ private foldDataRaw;
34
+ private processUploadSource;
35
+ private stringToBlob;
36
+ private globalBlob;
37
+ private nodeJsBlob;
38
+ private mapAsync;
39
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./ChunkedStream";
2
+ export * from "./Model";
3
+ export * from "./UploadManager";
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "@bytescale/sdk",
3
+ "version": "1.0.0-alpha.1",
4
+ "description": "Bytescale JavaScript SDK",
5
+ "author": "Bytescale <hello@bytescale.com> (https://www.bytescale.com)",
6
+ "license": "MIT",
7
+ "types": "dist/types/index.d.ts",
8
+ "main": "dist/node/cjs/main.js",
9
+ "module": "dist/node/esm/main.mjs",
10
+ "browser": "dist/browser/cjs/main.js",
11
+ "exports": {
12
+ ".": {
13
+ "node": {
14
+ "require": "./dist/node/cjs/main.js",
15
+ "import": "./dist/node/esm/main.mjs"
16
+ },
17
+ "browser": {
18
+ "require": "./dist/browser/cjs/main.js"
19
+ },
20
+ "default": "./dist/node/esm/main.mjs"
21
+ }
22
+ },
23
+ "homepage": "https://github.com/bytescale/bytescale-javascript-sdk",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/bytescale/bytescale-javascript-sdk.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/bytescale/bytescale-javascript-sdk/issues"
30
+ },
31
+ "files": [
32
+ "/dist/**/*",
33
+ "/tests/**/*"
34
+ ],
35
+ "scripts": {
36
+ "clean": "rm -rf dist && rm -f bytescale-sdk.tgz",
37
+ "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
38
+ "typecheck": "tsc --noEmit",
39
+ "test": "NODE_OPTIONS=--experimental-vm-modules npx jest --runInBand --silent=false --verbose=false",
40
+ "prepack": "npm run clean && webpack && tsc-alias",
41
+ "postpack": "[ ! -f bytescale-sdk-*.tgz ] || mv bytescale-sdk-*.tgz bytescale-sdk.tgz",
42
+ "prepare": "husky install",
43
+ "prepack:cdn": "npm run clean && webpack --config webpack.config.cdn.js && find dist -name \"*.ts\" -type f -delete && for f in dist/*.js; do cp -- \"$f\" \"${f%.js}\"; done",
44
+ "publish:executeIfReleaseCommit": "bash -c 'COMMIT=$(git log -1 --pretty=%B) && [ \"${COMMIT:0:8}\" != \"Release \" ] || npm run publish:execute'",
45
+ "publish:execute": "npm run publish:cdn && npm publish && npm run publish:createGitHubRelease",
46
+ "publish:createGitHubRelease": "gh release create v$(node -p \"require('./package.json').version\")",
47
+ "publish:cdn": "npm run prepack:cdn && aws s3 cp --recursive --content-type application/javascript dist/ s3://bytescale-js-releases/sdk/ && aws cloudfront create-invalidation --distribution-id EO4VJPLCDN8HY --paths '/sdk/*'"
48
+ },
49
+ "husky": {
50
+ "hooks": {
51
+ "pre-commit": "lint-staged"
52
+ }
53
+ },
54
+ "lint-staged": {
55
+ "*/**/*.{ts,tsx}": [
56
+ "bash -c \"tsc --noEmit\""
57
+ ],
58
+ "*/**/*.{js,jsx,ts,tsx}": [
59
+ "eslint"
60
+ ],
61
+ "*.{js,jsx,ts,tsx,json,css,html,md,yaml,yml}": [
62
+ "prettier -w"
63
+ ]
64
+ },
65
+ "devDependencies": {
66
+ "@babel/cli": "7.19.3",
67
+ "@babel/core": "7.20.5",
68
+ "@babel/preset-env": "7.20.2",
69
+ "@types/jest": "29.2.4",
70
+ "@typescript-eslint/eslint-plugin": "4.33.0",
71
+ "@typescript-eslint/parser": "4.33.0",
72
+ "babel-loader": "8.2.1",
73
+ "babel-plugin-transform-async-to-promises": "0.8.15",
74
+ "eslint": "7.32.0",
75
+ "eslint-config-prettier": "6.15.0",
76
+ "eslint-config-standard-with-typescript": "19.0.1",
77
+ "eslint-plugin-import": "2.22.1",
78
+ "eslint-plugin-node": "11.1.0",
79
+ "eslint-plugin-promise": "4.2.1",
80
+ "eslint-plugin-return-types-object-literals": "1.0.1",
81
+ "eslint-plugin-standard": "4.1.0",
82
+ "husky": "7.0.4",
83
+ "jest": "29.3.1",
84
+ "lint-staged": "10.5.1",
85
+ "node-fetch": "3.3.0",
86
+ "prettier": "2.8.1",
87
+ "ts-jest": "29.0.3",
88
+ "ts-loader": "9.2.2",
89
+ "tsc-alias": "1.2.10",
90
+ "typescript": "4.4.4",
91
+ "webpack": "5.75.0",
92
+ "webpack-cli": "4.10.0",
93
+ "webpack-node-externals": "2.5.2",
94
+ "webpack-shell-plugin-next": "2.3.1",
95
+ "wrapper-webpack-plugin": "2.2.2"
96
+ }
97
+ }
@@ -0,0 +1,49 @@
1
+ import { ChunkedStream } from "../src";
2
+ import { createRandomStreamFactory } from "./utils/RandomStream";
3
+ import { streamToBuffer } from "./utils/StreamToBuffer";
4
+
5
+ describe("ChunkedStream", () => {
6
+ test(
7
+ "forward the entire stream",
8
+ async () => {
9
+ const expectedSize = Math.pow(1024, 2) * 100; // 100MB
10
+ const maxPartSize = Math.pow(1024, 2) * 2; // 2MB
11
+ const expectedData = await createRandomStreamFactory(expectedSize);
12
+ const chunkedStream = new ChunkedStream(expectedData());
13
+ const worker = chunkedStream.runChunkPipeline();
14
+ let remaining = expectedSize;
15
+ const buffersFromParts = Array<Buffer>();
16
+
17
+ while (remaining > 0) {
18
+ const nextPartSize = (): number => {
19
+ const next = Math.min(remaining, Math.round(Math.random() * maxPartSize) + 1);
20
+ remaining -= next;
21
+ return next;
22
+ };
23
+
24
+ const partSize = nextPartSize();
25
+
26
+ const subStream = await chunkedStream.take(partSize);
27
+ const subStreamBuffer = await streamToBuffer(subStream);
28
+
29
+ expect(subStreamBuffer.byteLength).toEqual(partSize);
30
+ expect(subStreamBuffer.length).toEqual(partSize);
31
+
32
+ buffersFromParts.push(subStreamBuffer);
33
+ }
34
+
35
+ chunkedStream.finishedConsuming();
36
+ await worker;
37
+
38
+ const actual = Buffer.concat(buffersFromParts);
39
+ const expected = await streamToBuffer(expectedData());
40
+
41
+ const buffersEqual = Buffer.compare(expected, actual) === 0;
42
+
43
+ expect(actual.length).toEqual(expected.length);
44
+ expect(actual.byteLength).toEqual(expected.byteLength);
45
+ expect(buffersEqual).toEqual(true);
46
+ },
47
+ 10 * 60 * 1000
48
+ );
49
+ });
@@ -0,0 +1,164 @@
1
+ import { UploadManager, Configuration, FileApi } from "../src";
2
+ import fetch from "node-fetch";
3
+ import * as buffer from "buffer";
4
+ import { createRandomStreamFactory } from "./utils/RandomStream";
5
+ import { streamToBuffer } from "./utils/StreamToBuffer";
6
+ import { promises as fsAsync } from "fs";
7
+ import { prepareTempDirectory } from "./utils/TempUtils";
8
+ import * as Path from "path";
9
+
10
+ if (process.env.BYTESCALE_SECRET_API_KEY === undefined) {
11
+ throw new Error("Expected env var: BYTESCALE_SECRET_API_KEY");
12
+ }
13
+ if (process.env.BYTESCALE_ACCOUNT_ID === undefined) {
14
+ throw new Error("Expected env var: BYTESCALE_ACCOUNT_ID");
15
+ }
16
+ const accountId = process.env.BYTESCALE_ACCOUNT_ID;
17
+
18
+ const configuration = new Configuration({
19
+ fetchApi: fetch as any,
20
+ apiKey: process.env.BYTESCALE_SECRET_API_KEY // e.g. "secret_xxxxx"
21
+ });
22
+
23
+ const uploadManager = new UploadManager(configuration);
24
+ const fileApi = new FileApi(configuration);
25
+ const largeFileSize = Math.pow(1024, 2) * 500; // 500MB
26
+
27
+ async function testStreamingUpload(expectedSize: number): Promise<void> {
28
+ const expectedData = await createRandomStreamFactory(expectedSize);
29
+ const uploadedFile = await uploadManager.upload({
30
+ accountId,
31
+ data: expectedData(),
32
+ size: expectedSize
33
+ });
34
+ const fileDetails = await fileApi.getFileDetails({ accountId, filePath: uploadedFile.filePath });
35
+ const actualData = (await fileApi.downloadFile({ accountId, filePath: uploadedFile.filePath })).stream();
36
+ const expectedDataBuffer = await streamToBuffer(expectedData());
37
+ const actualDataBuffer = await streamToBuffer(actualData as any);
38
+ const buffersEqual = Buffer.compare(expectedDataBuffer, actualDataBuffer) === 0;
39
+ const actualSize = fileDetails.size;
40
+
41
+ expect(actualSize).toEqual(expectedSize);
42
+ expect(actualDataBuffer.length).toEqual(expectedDataBuffer.length);
43
+ expect(actualDataBuffer.byteLength).toEqual(expectedDataBuffer.byteLength);
44
+
45
+ if (!buffersEqual) {
46
+ const dir = await prepareTempDirectory();
47
+ await fsAsync.writeFile(Path.join(dir, "expected.txt"), expectedDataBuffer);
48
+ await fsAsync.writeFile(Path.join(dir, "actual.txt"), actualDataBuffer);
49
+ }
50
+
51
+ expect(buffersEqual).toEqual(true);
52
+ }
53
+
54
+ describe("UploadManager", () => {
55
+ test("upload an empty string", async () => {
56
+ const expectedData = "";
57
+ const expectedSize = 0;
58
+ const uploadedFile = await uploadManager.upload({
59
+ accountId,
60
+ data: expectedData
61
+ });
62
+ const fileDetails = await fileApi.getFileDetails({ accountId, filePath: uploadedFile.filePath });
63
+ const actualData = await (await fileApi.downloadFile({ accountId, filePath: uploadedFile.filePath })).text();
64
+ const actualSize = fileDetails.size;
65
+ expect(actualData).toEqual(expectedData);
66
+ expect(actualSize).toEqual(expectedSize);
67
+ });
68
+
69
+ test("upload a string", async () => {
70
+ const expectedData = "Example Data";
71
+ const expectedMime = "text/plain";
72
+ const uploadedFile = await uploadManager.upload({
73
+ accountId,
74
+ data: expectedData
75
+ });
76
+ const fileDetails = await fileApi.getFileDetails({ accountId, filePath: uploadedFile.filePath });
77
+ const actualData = await (await fileApi.downloadFile({ accountId, filePath: uploadedFile.filePath })).text();
78
+ const actualMime = fileDetails.mime;
79
+ expect(actualData).toEqual(expectedData);
80
+ expect(actualMime).toEqual(expectedMime);
81
+ });
82
+
83
+ test("upload a BLOB", async () => {
84
+ const expectedData = JSON.stringify({ someValue: 42 });
85
+ const expectedMime = "application/json";
86
+ const uploadedFile = await uploadManager.upload({
87
+ accountId,
88
+ data: new buffer.Blob([expectedData], { type: expectedMime })
89
+ });
90
+ const fileDetails = await fileApi.getFileDetails({ accountId, filePath: uploadedFile.filePath });
91
+ const actualData = await (await fileApi.downloadFile({ accountId, filePath: uploadedFile.filePath })).text();
92
+ const actualMime = fileDetails.mime;
93
+ expect(actualData).toEqual(expectedData);
94
+ expect(actualMime).toEqual(expectedMime);
95
+ });
96
+
97
+ test("upload a buffer (override MIME)", async () => {
98
+ const expectedData = "Example Data";
99
+ const expectedMime = "text/plain";
100
+ const uploadedFile = await uploadManager.upload({
101
+ accountId,
102
+ data: Buffer.from(expectedData, "utf8"),
103
+ mime: expectedMime
104
+ });
105
+ const fileDetails = await fileApi.getFileDetails({ accountId, filePath: uploadedFile.filePath });
106
+ const actualData = await (await fileApi.downloadFile({ accountId, filePath: uploadedFile.filePath })).text();
107
+ const actualMime = fileDetails.mime;
108
+ expect(actualData).toEqual(expectedData);
109
+ expect(actualMime).toEqual(expectedMime);
110
+ });
111
+
112
+ test("upload a small buffer", async () => {
113
+ const expectedData = "Example Data";
114
+ const expectedMime = "application/octet-stream";
115
+ const uploadedFile = await uploadManager.upload({
116
+ accountId,
117
+ data: Buffer.from(expectedData, "utf8")
118
+ });
119
+ const fileDetails = await fileApi.getFileDetails({ accountId, filePath: uploadedFile.filePath });
120
+ const actualData = await (await fileApi.downloadFile({ accountId, filePath: uploadedFile.filePath })).text();
121
+ const actualMime = fileDetails.mime;
122
+ expect(actualData).toEqual(expectedData);
123
+ expect(actualMime).toEqual(expectedMime);
124
+ });
125
+
126
+ test(
127
+ "upload a large buffer",
128
+ async () => {
129
+ const expectedSize = largeFileSize;
130
+ const expectedData = await streamToBuffer((await createRandomStreamFactory(expectedSize))());
131
+ const uploadedFile = await uploadManager.upload({
132
+ accountId,
133
+ data: expectedData
134
+ });
135
+ const fileDetails = await fileApi.getFileDetails({ accountId, filePath: uploadedFile.filePath });
136
+ const actualStream = (await fileApi.downloadFile({ accountId, filePath: uploadedFile.filePath })).stream();
137
+ const actualData = await streamToBuffer(actualStream as any);
138
+ const actualSize = fileDetails.size;
139
+ const buffersEqual = Buffer.compare(expectedData, actualData) === 0;
140
+
141
+ expect(actualSize).toEqual(expectedSize);
142
+ expect(actualData.length).toEqual(expectedData.length);
143
+ expect(actualData.byteLength).toEqual(expectedData.byteLength);
144
+ expect(buffersEqual).toEqual(true);
145
+ },
146
+ 10 * 60 * 1000
147
+ );
148
+
149
+ test(
150
+ "upload a small stream",
151
+ async () => {
152
+ await testStreamingUpload(Math.pow(1024, 2) * 2); // 2MB
153
+ },
154
+ 10 * 60 * 1000
155
+ );
156
+
157
+ test(
158
+ "upload a large stream",
159
+ async () => {
160
+ await testStreamingUpload(largeFileSize); // 500MB
161
+ },
162
+ 10 * 60 * 1000
163
+ );
164
+ });