@or-sdk/files 3.7.3 → 3.8.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/src/Files.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Base } from '@or-sdk/base';
2
2
  import axios from 'axios';
3
- import type { AxiosProgressEvent } from 'axios';
4
- import { isNode } from 'browser-or-node';
3
+ import type FormDataNode from 'form-data';
4
+ import { Memoize } from 'typescript-memoize';
5
5
 
6
6
  import { SERVICE_KEY } from './constants';
7
- import {
7
+ import type {
8
8
  FileItem,
9
9
  FileItemSelect,
10
10
  FilesConfig,
@@ -13,8 +13,11 @@ import {
13
13
  UploadFileProps,
14
14
  UploadSystemFileParams,
15
15
  UploadSystemUrlResponse,
16
+ UploadToSignedUrlParameters,
17
+ UploadUrlProps,
16
18
  UploadUrlResponse,
17
19
  } from './types';
20
+ import { isNode } from './utils';
18
21
 
19
22
  export class Files extends Base {
20
23
  /**
@@ -72,7 +75,7 @@ export class Files extends Base {
72
75
  * @return total folder size in bytes
73
76
  */
74
77
  async getFolderSize(key: string): Promise<number> {
75
- const { size } = await this.callApiV2<{size: number;}>({
78
+ const { size } = await this.callApiV2<{ size: number; }>({
76
79
  method: 'get',
77
80
  route: 'folders/find-one',
78
81
  params: { key },
@@ -104,34 +107,19 @@ export class Files extends Base {
104
107
  attributes,
105
108
  };
106
109
 
107
- let res: [(FileItem[] | FileItemSelect[]), FileItem[]];
110
+ const [files, folders] = await Promise.all([
111
+ this.callApiV2<FileItem[] | FileItemSelect[]>({
112
+ method: 'GET',
113
+ route: 'files',
114
+ params: queryParams,
115
+ }),
116
+ isPublic ? [] as FileItem[] : this.callApiV2<FileItem[]>({
117
+ method: 'GET',
118
+ route: 'folders',
119
+ params: { prefix: treePrefix },
120
+ }),
121
+ ]);
108
122
 
109
- if (isPublic) {
110
- res = await Promise.all([
111
- this.callApiV2<FileItem[] | FileItemSelect[]>({
112
- method: 'GET',
113
- route: 'files',
114
- params: queryParams,
115
- }),
116
- [], // we prevent unnecessary request to get public folders, because they only private
117
- ]);
118
- } else {
119
- res = await Promise.all([
120
- this.callApiV2<FileItem[] | FileItemSelect[]>({
121
- method: 'GET',
122
- route: 'files',
123
- params: queryParams,
124
- }),
125
- this.callApiV2<FileItem[]>({
126
- method: 'GET',
127
- route: 'folders',
128
- params: { prefix: treePrefix },
129
- }),
130
- ]);
131
- }
132
-
133
-
134
- const [files, folders] = res;
135
123
  return [...folders, ...files];
136
124
  }
137
125
 
@@ -155,17 +143,21 @@ export class Files extends Base {
155
143
  prefix: term,
156
144
  };
157
145
 
158
- const files: FileItem[] = await this.callApiV2({
159
- method: 'GET',
160
- route: 'files/search',
161
- params: queryParams,
162
- });
146
+ const [files, folders] = await Promise.all([
147
+ this.callApiV2<FileItem[]>({
148
+ method: 'GET',
149
+ route: 'files/search',
150
+ params: queryParams,
151
+ }),
163
152
 
164
- const folders: FileItem[] = isPublic ? [] : await this.callApiV2({
165
- method: 'GET',
166
- route: 'folders/search',
167
- params: queryParams,
168
- });
153
+ isPublic
154
+ ? [] as FileItem[]
155
+ : this.callApiV2<FileItem[]>({
156
+ method: 'GET',
157
+ route: 'folders/search',
158
+ params: queryParams,
159
+ }),
160
+ ]);
169
161
 
170
162
  return [...files, ...folders];
171
163
  }
@@ -233,7 +225,7 @@ export class Files extends Base {
233
225
  const STEP = 2000; // 2 second in ms
234
226
  let passed = 0;
235
227
 
236
- const promise = new Promise((res, rej) => {
228
+ await new Promise((res, rej) => {
237
229
  // it may take some time to copy file on S3, so we check it each "STEP" time
238
230
  const interval = setInterval(() => {
239
231
  this.getFile(newKey, isPublic)
@@ -245,8 +237,6 @@ export class Files extends Base {
245
237
  .catch(() => passed += STEP);
246
238
  }, STEP);
247
239
  });
248
-
249
- await promise;
250
240
  }
251
241
 
252
242
  /**
@@ -325,9 +315,12 @@ export class Files extends Base {
325
315
  * @param ttl timestamp of file expiration
326
316
  * @return uploading Url with different header Fields
327
317
  */
328
- getUploadUrl(data: { rewriteMode?: 'rewrite' | 'prevent-rewrite'; maxFileSize: number | undefined;
329
- contentType: string; key: string; cacheControl: string; },
330
- isPublic: boolean, ttl?: number, abortSignal?: AbortSignal): Promise<UploadUrlResponse> {
318
+ getUploadUrl(
319
+ data: UploadUrlProps,
320
+ isPublic: boolean,
321
+ ttl?: number,
322
+ abortSignal?: AbortSignal,
323
+ ): Promise<UploadUrlResponse> {
331
324
  const reqData: GetUploadUrlDataPayload = { ...data };
332
325
  if (ttl) reqData.ttl = new Date(ttl).toISOString();
333
326
 
@@ -358,7 +351,6 @@ export class Files extends Base {
358
351
  * @param waitTillFileAddedInDb true if you want to make sure file added to DB,
359
352
  * for example for instant removing it. Please, use it carefully.
360
353
  */
361
- // eslint-disable-next-line max-len
362
354
  async uploadFile({
363
355
  type,
364
356
  name,
@@ -369,32 +361,35 @@ export class Files extends Base {
369
361
  rewriteMode,
370
362
  ttl,
371
363
  maxFileSize,
364
+ knownLength,
365
+ cacheControl = 'no-cache',
372
366
  waitTillFileAddedInDb,
373
367
  }: UploadFileProps, abortSignal?: AbortSignal): Promise<string> {
374
368
  const contentType = type || 'binary/octet-stream';
375
369
  const fileKey = prefix + name;
376
370
 
377
- const { url, fields, downloadUrl } = await this.getUploadUrl({
371
+ const signedUrl = await this.getUploadUrl(
372
+ {
373
+ contentType,
374
+ key: fileKey,
375
+ cacheControl,
376
+ rewriteMode: rewriteMode,
377
+ maxFileSize: maxFileSize,
378
+ },
379
+ isPublic,
380
+ ttl,
381
+ abortSignal,
382
+ );
383
+
384
+ await this.uploadToSignedUrl({
385
+ signedUrl,
386
+ file: fileModel,
387
+ fileName: name,
388
+ cacheControl,
378
389
  contentType,
379
- key: fileKey,
380
- cacheControl: 'no-cache',
381
- rewriteMode: rewriteMode,
382
- maxFileSize: maxFileSize,
383
- }, isPublic, ttl, abortSignal);
384
-
385
- const FormDataLib = isNode ? require('form-data') : FormData; // eslint-disable-line no-undef
386
- const formData = new FormDataLib();
387
-
388
- Object.entries(fields).forEach(value => {
389
- formData.append(value[0], value[1]);
390
- });
391
-
392
- formData.append('content-type', contentType);
393
- formData.append('File', fileModel, name);
394
-
395
- await axios.post(url, formData, {
390
+ knownLength,
396
391
  signal: abortSignal,
397
- onUploadProgress: (event: AxiosProgressEvent) => progress?.(event),
392
+ onUploadProgress: progress,
398
393
  });
399
394
 
400
395
  if (waitTillFileAddedInDb) {
@@ -402,7 +397,7 @@ export class Files extends Base {
402
397
  const STEP = 2000; // 2 second in ms
403
398
  let passed = 0;
404
399
 
405
- const promise = new Promise((res, rej) => {
400
+ await new Promise((res, rej) => {
406
401
  // it may take some time to copy file on S3, so we check it each "STEP" time
407
402
  const interval = setInterval(() => {
408
403
  this.getFile(fileKey, isPublic)
@@ -414,11 +409,9 @@ export class Files extends Base {
414
409
  .catch(() => passed += STEP);
415
410
  }, STEP);
416
411
  });
417
-
418
- await promise;
419
412
  }
420
413
 
421
- return downloadUrl;
414
+ return signedUrl.downloadUrl;
422
415
  }
423
416
 
424
417
  // ------------------------
@@ -431,14 +424,15 @@ export class Files extends Base {
431
424
  * @param file file for uploading
432
425
  * @param cacheControl cache settings
433
426
  * @param abortSignal signal to cancel uploading
427
+ * @deprecated use `uploadSystemFileV2` instead
434
428
  */
435
429
  async uploadSystemFile(
436
430
  prefix: string,
437
431
  file: File,
438
432
  cacheControl = 'max-age=3600',
439
- abortSignal?: AbortSignal
433
+ abortSignal?: AbortSignal,
440
434
  ): Promise<string> {
441
- const result: UploadSystemUrlResponse = await this.callApiV2({
435
+ const signedUrl: UploadSystemUrlResponse = await this.callApiV2({
442
436
  method: 'post',
443
437
  route: 'system-file',
444
438
  data: {
@@ -449,24 +443,16 @@ export class Files extends Base {
449
443
  signal: abortSignal,
450
444
  });
451
445
 
452
- const { url, fields, downloadUrl } = result;
453
-
454
- const FormDataLib = isNode ? require('form-data') : FormData; // eslint-disable-line no-undef
455
- const formData = new FormDataLib();
456
-
457
- Object.entries(fields).forEach(value => {
458
- formData.append(value[0], value[1]);
459
- });
460
-
461
- formData.append('cache-control', cacheControl);
462
- formData.append('content-type', file.type);
463
- formData.append('File', file, file.name);
464
-
465
- await axios.post(url, formData, {
446
+ await this.uploadToSignedUrl({
447
+ signedUrl,
448
+ file,
449
+ fileName: file.name,
450
+ cacheControl,
451
+ contentType: file.type,
466
452
  signal: abortSignal,
467
453
  });
468
454
 
469
- return downloadUrl;
455
+ return signedUrl.downloadUrl;
470
456
  }
471
457
 
472
458
  /**
@@ -487,9 +473,10 @@ export class Files extends Base {
487
473
  ttl,
488
474
  fileName,
489
475
  contentType,
476
+ knownLength,
490
477
  abortSignal,
491
478
  }: UploadSystemFileParams): Promise<string> {
492
- const result: UploadSystemUrlResponse = await this.callApiV2({
479
+ const signedUrl: UploadSystemUrlResponse = await this.callApiV2({
493
480
  method: 'post',
494
481
  route: 'system-file',
495
482
  data: {
@@ -501,24 +488,17 @@ export class Files extends Base {
501
488
  signal: abortSignal,
502
489
  });
503
490
 
504
- const { url, fields, downloadUrl } = result;
505
-
506
- const FormDataLib = isNode ? require('form-data') : FormData; // eslint-disable-line no-undef
507
- const formData = new FormDataLib();
508
-
509
- Object.entries(fields).forEach(value => {
510
- formData.append(value[0], value[1]);
511
- });
512
-
513
- formData.append('cache-control', cacheControl);
514
- formData.append('content-type', contentType);
515
- formData.append('File', file, fileName);
516
-
517
- await axios.post(url, formData, {
491
+ await this.uploadToSignedUrl({
492
+ signedUrl,
493
+ file,
494
+ fileName,
495
+ cacheControl,
496
+ contentType,
497
+ knownLength,
518
498
  signal: abortSignal,
519
499
  });
520
500
 
521
- return downloadUrl;
501
+ return signedUrl.downloadUrl;
522
502
  }
523
503
 
524
504
  /**
@@ -584,4 +564,68 @@ export class Files extends Base {
584
564
  params: { key: path },
585
565
  });
586
566
  }
567
+
568
+ private async uploadToSignedUrl({
569
+ signedUrl,
570
+ file,
571
+ fileName,
572
+ cacheControl,
573
+ contentType,
574
+ knownLength,
575
+ signal,
576
+ onUploadProgress,
577
+ }: UploadToSignedUrlParameters) {
578
+ const { url, fields } = signedUrl;
579
+
580
+ const FormDataLib = await this.formDataFactory();
581
+ const formData = new FormDataLib();
582
+
583
+ Object.entries(fields).forEach(([key, value]) => {
584
+ formData.append(key, value);
585
+ });
586
+
587
+ if (cacheControl != undefined && cacheControl !== 'no-cache') {
588
+ formData.append('cache-control', cacheControl);
589
+ }
590
+ if (contentType) formData.append('content-type', contentType);
591
+
592
+ if (isNode) {
593
+ (formData as FormDataNode).append('File', file, {
594
+ filename: fileName,
595
+ contentType,
596
+ knownLength,
597
+ });
598
+ } else {
599
+ if (!(file instanceof File || file instanceof Blob)) {
600
+ throw new Error('In browser file can only be an instance of File or Blob');
601
+ }
602
+ (formData as FormData).append('File', file, fileName);
603
+ }
604
+
605
+ return await axios.post(url, formData, {
606
+ headers: {
607
+ ...this.getFormDataHeaders(formData),
608
+ },
609
+ onUploadProgress,
610
+ maxBodyLength: Infinity,
611
+ maxContentLength: Infinity,
612
+ signal,
613
+ });
614
+ }
615
+
616
+ @Memoize()
617
+ private async formDataFactory(): Promise<typeof FormDataNode | typeof FormData> {
618
+ if (isNode) {
619
+ const formDataModule = await import('form-data');
620
+ return formDataModule.default;
621
+ } else {
622
+ return FormData;
623
+ }
624
+ }
625
+
626
+ private getFormDataHeaders(formData: FormDataNode | FormData): Record<string, string> {
627
+ return ('getHeaders' in formData)
628
+ ? formData.getHeaders()
629
+ : {};
630
+ }
587
631
  }
package/src/types.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { Token } from '@or-sdk/base';
1
+ import type { Token } from '@or-sdk/base';
2
2
  import type { AxiosProgressEvent } from 'axios';
3
+ import type { ReadStream } from 'node:fs';
3
4
 
4
5
  export type FilesConfig = {
5
6
  /**
@@ -69,7 +70,7 @@ export type UploadUrlProps = {
69
70
  key: string;
70
71
  contentType: string;
71
72
  maxFileSize?: number;
72
- cacheControl: string;
73
+ cacheControl?: string;
73
74
  rewriteMode?: 'rewrite' | 'prevent-rewrite';
74
75
  };
75
76
 
@@ -85,12 +86,7 @@ export type UploadFields = {
85
86
  'cache-control'?: string;
86
87
  };
87
88
 
88
- export type GetUploadUrlDataPayload = {
89
- rewriteMode?: 'rewrite' | 'prevent-rewrite';
90
- maxFileSize?: number;
91
- contentType: string;
92
- key: string;
93
- cacheControl: string;
89
+ export type GetUploadUrlDataPayload = UploadUrlProps & {
94
90
  ttl?: string;
95
91
  };
96
92
 
@@ -106,26 +102,45 @@ export type UploadSystemUrlResponse = {
106
102
  downloadUrl: string;
107
103
  };
108
104
 
105
+ /** Buffer and ReadStream only supported in Node.js */
106
+ export type FileModel = File | Blob | Buffer | ReadStream
107
+
109
108
  export type UploadFileProps = {
109
+ /** mime type of the file */
110
110
  type: string;
111
111
  name: string;
112
- fileModel: File | Buffer;
112
+ fileModel: FileModel;
113
113
  prefix: string;
114
114
  isPublic: boolean;
115
115
  progress?: (event: ProgressEvent | AxiosProgressEvent) => void;
116
116
  rewriteMode?: 'rewrite' | 'prevent-rewrite';
117
117
  ttl?: number;
118
118
  maxFileSize?: number;
119
+ knownLength?: number;
120
+ cacheControl?: string;
119
121
  abortSignal?: AbortSignal;
120
122
  waitTillFileAddedInDb?: boolean;
121
123
  };
122
124
 
123
125
  export type UploadSystemFileParams = {
124
126
  prefix: string;
125
- file: Buffer | Blob;
127
+ file: FileModel;
126
128
  cacheControl?: string;
127
129
  ttl: number;
128
130
  fileName: string;
129
131
  contentType?: string;
132
+ knownLength?: number;
130
133
  abortSignal?: AbortSignal;
131
134
  };
135
+
136
+ export type UploadToSignedUrlParameters = {
137
+ signedUrl: UploadUrlResponse;
138
+ file: FileModel;
139
+ fileName: string;
140
+ cacheControl?: string;
141
+ /** Size of the file in bytes. Helpful when file is an instance of ReadStream in Node.js */
142
+ knownLength?: number;
143
+ contentType?: string;
144
+ signal?: AbortSignal;
145
+ onUploadProgress?: (event: ProgressEvent | AxiosProgressEvent) => void;
146
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,3 @@
1
+ export const isNode = typeof process !== 'undefined'
2
+ && process.versions != undefined
3
+ && process.versions.node != undefined;
package/tsconfig.esm.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "compilerOptions": {
4
4
  "outDir": "./dist/esm",
5
5
  "declarationDir": "./dist/types",
6
- "module": "ES6",
6
+ "module": "esnext",
7
7
  "target": "es6",
8
8
  "rootDir": "./src",
9
9
  "declaration": true,