@aws-amplify/storage 5.4.1 → 5.5.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/Storage.ts CHANGED
@@ -23,6 +23,8 @@ import {
23
23
  StorageListOutput,
24
24
  StorageCopyOutput,
25
25
  UploadTask,
26
+ StorageGetPropertiesConfig,
27
+ StorageGetPropertiesOutput,
26
28
  } from './types';
27
29
  import axios, { CancelTokenSource } from 'axios';
28
30
  import { PutObjectCommandInput } from '@aws-sdk/client-s3';
@@ -246,22 +248,22 @@ export class Storage {
246
248
  config?: StorageCopyConfig<T>
247
249
  ): StorageCopyOutput<T> {
248
250
  const provider = config?.provider || DEFAULT_PROVIDER;
249
- const prov = this._pluggables.find(
251
+ const plugin = this._pluggables.find(
250
252
  pluggable => pluggable.getProviderName() === provider
251
253
  );
252
- if (prov === undefined) {
254
+ if (plugin === undefined) {
253
255
  logger.debug('No plugin found with providerName', provider);
254
256
  return Promise.reject(
255
257
  'No plugin found in Storage for the provider'
256
258
  ) as StorageCopyOutput<T>;
257
259
  }
258
260
  const cancelTokenSource = this.getCancellableTokenSource();
259
- if (typeof prov.copy !== 'function') {
261
+ if (typeof plugin.copy !== 'function') {
260
262
  return Promise.reject(
261
- `.copy is not implemented on provider ${prov.getProviderName()}`
263
+ `.copy is not implemented on provider ${plugin.getProviderName()}`
262
264
  ) as StorageCopyOutput<T>;
263
265
  }
264
- const responsePromise = prov.copy(src, dest, {
266
+ const responsePromise = plugin.copy(src, dest, {
265
267
  ...config,
266
268
  cancelTokenSource,
267
269
  });
@@ -285,17 +287,17 @@ export class Storage {
285
287
  T extends StorageProvider | { [key: string]: any; download?: boolean }
286
288
  >(key: string, config?: StorageGetConfig<T>): StorageGetOutput<T> {
287
289
  const provider = config?.provider || DEFAULT_PROVIDER;
288
- const prov = this._pluggables.find(
290
+ const plugin = this._pluggables.find(
289
291
  pluggable => pluggable.getProviderName() === provider
290
292
  );
291
- if (prov === undefined) {
293
+ if (plugin === undefined) {
292
294
  logger.debug('No plugin found with providerName', provider);
293
295
  return Promise.reject(
294
296
  'No plugin found in Storage for the provider'
295
297
  ) as StorageGetOutput<T>;
296
298
  }
297
299
  const cancelTokenSource = this.getCancellableTokenSource();
298
- const responsePromise = prov.get(key, {
300
+ const responsePromise = plugin.get(key, {
299
301
  ...config,
300
302
  cancelTokenSource,
301
303
  });
@@ -307,6 +309,30 @@ export class Storage {
307
309
  return axios.isCancel(error);
308
310
  }
309
311
 
312
+ public getProperties<T extends StorageProvider | { [key: string]: any }>(
313
+ key: string,
314
+ config?: StorageGetPropertiesConfig<T>
315
+ ): StorageGetPropertiesOutput<T> {
316
+ const provider = config?.provider || DEFAULT_PROVIDER;
317
+ const plugin = this._pluggables.find(
318
+ pluggable => pluggable.getProviderName() === provider
319
+ );
320
+ if (plugin === undefined) {
321
+ logger.debug('No plugin found with providerName', provider);
322
+ throw new Error('No plugin found with providerName');
323
+ }
324
+ const cancelTokenSource = this.getCancellableTokenSource();
325
+ if (typeof plugin.getProperties !== 'function') {
326
+ return Promise.reject(
327
+ `.getProperties is not implemented on provider ${plugin.getProviderName()}`
328
+ ) as StorageGetPropertiesOutput<T>;
329
+ }
330
+ const responsePromise = plugin?.getProperties(key, {
331
+ ...config,
332
+ });
333
+ this.updateRequestToBeCancellable(responsePromise, cancelTokenSource);
334
+ return responsePromise as StorageGetPropertiesOutput<T>;
335
+ }
310
336
  /**
311
337
  * Put a file in storage bucket specified to configure method
312
338
  * @param key - key of the object
@@ -326,17 +352,17 @@ export class Storage {
326
352
  config?: StoragePutConfig<T>
327
353
  ): StoragePutOutput<T> {
328
354
  const provider = config?.provider || DEFAULT_PROVIDER;
329
- const prov = this._pluggables.find(
355
+ const plugin = this._pluggables.find(
330
356
  pluggable => pluggable.getProviderName() === provider
331
357
  );
332
- if (prov === undefined) {
358
+ if (plugin === undefined) {
333
359
  logger.debug('No plugin found with providerName', provider);
334
360
  return Promise.reject(
335
361
  'No plugin found in Storage for the provider'
336
362
  ) as StoragePutOutput<T>;
337
363
  }
338
364
  const cancelTokenSource = this.getCancellableTokenSource();
339
- const response = prov.put(key, object, {
365
+ const response = plugin.put(key, object, {
340
366
  ...config,
341
367
  cancelTokenSource,
342
368
  });
@@ -361,16 +387,16 @@ export class Storage {
361
387
  config?: StorageRemoveConfig<T>
362
388
  ): StorageRemoveOutput<T> {
363
389
  const provider = config?.provider || DEFAULT_PROVIDER;
364
- const prov = this._pluggables.find(
390
+ const plugin = this._pluggables.find(
365
391
  pluggable => pluggable.getProviderName() === provider
366
392
  );
367
- if (prov === undefined) {
393
+ if (plugin === undefined) {
368
394
  logger.debug('No plugin found with providerName', provider);
369
395
  return Promise.reject(
370
396
  'No plugin found in Storage for the provider'
371
397
  ) as StorageRemoveOutput<T>;
372
398
  }
373
- return prov.remove(key, config) as StorageRemoveOutput<T>;
399
+ return plugin.remove(key, config) as StorageRemoveOutput<T>;
374
400
  }
375
401
 
376
402
  /**
@@ -388,16 +414,16 @@ export class Storage {
388
414
  config?: StorageListConfig<T>
389
415
  ): StorageListOutput<T> {
390
416
  const provider = config?.provider || DEFAULT_PROVIDER;
391
- const prov = this._pluggables.find(
417
+ const plugin = this._pluggables.find(
392
418
  pluggable => pluggable.getProviderName() === provider
393
419
  );
394
- if (prov === undefined) {
420
+ if (plugin === undefined) {
395
421
  logger.debug('No plugin found with providerName', provider);
396
422
  return Promise.reject(
397
423
  'No plugin found in Storage for the provider'
398
424
  ) as StorageListOutput<T>;
399
425
  }
400
- return prov.list(path, config) as StorageListOutput<T>;
426
+ return plugin.list(path, config) as StorageListOutput<T>;
401
427
  }
402
428
  }
403
429
 
@@ -21,6 +21,7 @@ import {
21
21
  GetObjectCommandInput,
22
22
  ListObjectsV2Request,
23
23
  HeadObjectCommand,
24
+ HeadObjectCommandInput,
24
25
  } from '@aws-sdk/client-s3';
25
26
  import { formatUrl } from '@aws-sdk/util-format-url';
26
27
  import { createRequest } from '@aws-sdk/util-create-request';
@@ -49,6 +50,8 @@ import {
49
50
  UploadTask,
50
51
  S3ClientOptions,
51
52
  S3ProviderListOutput,
53
+ S3ProviderGetPropertiesOutput,
54
+ S3ProviderGetPropertiesConfig,
52
55
  } from '../types';
53
56
  import { StorageErrorStrings } from '../common/StorageErrorStrings';
54
57
  import { dispatchStorageEvent } from '../common/StorageUtils';
@@ -498,6 +501,87 @@ export class AWSS3Provider implements StorageProvider {
498
501
  }
499
502
  }
500
503
 
504
+ /**
505
+ * Get Properties of the object
506
+ *
507
+ * @param {string} key - key of the object
508
+ * @param {S3ProviderGetPropertiesConfig} [config] - Optional configuration for the underlying S3 command
509
+ * @return {Promise<S3ProviderGetPropertiesOutput>} - A promise resolves to contentType,
510
+ * contentLength, eTag, lastModified, metadata
511
+ */
512
+ public async getProperties(
513
+ key: string,
514
+ config?: S3ProviderGetPropertiesConfig
515
+ ): Promise<S3ProviderGetPropertiesOutput> {
516
+ const credentialsOK = await this._ensureCredentials();
517
+ if (!credentialsOK || !this._isWithCredentials(this._config)) {
518
+ throw new Error(StorageErrorStrings.NO_CREDENTIALS);
519
+ }
520
+ const opt = Object.assign({}, this._config, config);
521
+ const {
522
+ bucket,
523
+ track = false,
524
+ SSECustomerAlgorithm,
525
+ SSECustomerKey,
526
+ SSECustomerKeyMD5,
527
+ } = opt;
528
+ const prefix = this._prefix(opt);
529
+ const final_key = prefix + key;
530
+ const emitter = new events.EventEmitter();
531
+ const s3 = this._createNewS3Client(opt, emitter);
532
+ logger.debug(`getProperties ${key} from ${final_key}`);
533
+
534
+ const params: HeadObjectCommandInput = {
535
+ Bucket: bucket,
536
+ Key: final_key,
537
+ };
538
+
539
+ if (SSECustomerAlgorithm) {
540
+ params.SSECustomerAlgorithm = SSECustomerAlgorithm;
541
+ }
542
+ if (SSECustomerKey) {
543
+ params.SSECustomerKey = SSECustomerKey;
544
+ }
545
+ if (SSECustomerKeyMD5) {
546
+ params.SSECustomerKeyMD5 = SSECustomerKeyMD5;
547
+ }
548
+ // See: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/classes/headobjectcommand.html
549
+
550
+ const headObjectCommand = new HeadObjectCommand(params);
551
+ try {
552
+ const response = await s3.send(headObjectCommand);
553
+ const getPropertiesResponse: S3ProviderGetPropertiesOutput = {
554
+ contentLength: response.ContentLength,
555
+ contentType: response.ContentType,
556
+ eTag: response.ETag,
557
+ lastModified: response.LastModified,
558
+ metadata: response.Metadata,
559
+ };
560
+ dispatchStorageEvent(
561
+ track,
562
+ 'getProperties',
563
+ { method: 'getProperties', result: 'success' },
564
+ null,
565
+ `getProperties successful for ${key}`
566
+ );
567
+ return getPropertiesResponse;
568
+ } catch (error) {
569
+ if (error.$metadata.httpStatusCode === 404) {
570
+ dispatchStorageEvent(
571
+ track,
572
+ 'getProperties',
573
+ {
574
+ method: 'getProperties',
575
+ result: 'failed',
576
+ },
577
+ null,
578
+ `${key} not found`
579
+ );
580
+ }
581
+ throw error;
582
+ }
583
+ }
584
+
501
585
  /**
502
586
  * Put a file in S3 bucket specified to configure method
503
587
  * @param key - key of the object
@@ -5,6 +5,7 @@ import {
5
5
  CopyObjectRequest,
6
6
  _Object,
7
7
  DeleteObjectCommandOutput,
8
+ HeadObjectRequest,
8
9
  } from '@aws-sdk/client-s3';
9
10
  import { StorageOptions, StorageAccessLevel } from './Storage';
10
11
  import {
@@ -51,6 +52,12 @@ export type S3ProviderGetConfig = CommonStorageOptions & {
51
52
  validateObjectExistence?: boolean;
52
53
  };
53
54
 
55
+ export type S3ProviderGetPropertiesConfig = CommonStorageOptions & {
56
+ SSECustomerAlgorithm?: HeadObjectRequest['SSECustomerAlgorithm'];
57
+ SSECustomerKey?: HeadObjectRequest['SSECustomerKey'];
58
+ SSECustomerKeyMD5?: HeadObjectRequest['SSECustomerKeyMD5'];
59
+ };
60
+
54
61
  export type S3ProviderGetOuput<T> = T extends { download: true }
55
62
  ? GetObjectCommandOutput
56
63
  : string;
@@ -173,6 +180,14 @@ export type S3ProviderCopyOutput = {
173
180
  key: string;
174
181
  };
175
182
 
183
+ export type S3ProviderGetPropertiesOutput = {
184
+ contentType: string;
185
+ contentLength: number;
186
+ eTag: string;
187
+ lastModified: Date;
188
+ metadata: Record<string, string>;
189
+ };
190
+
176
191
  export type PutResult = {
177
192
  key: string;
178
193
  };
@@ -1,4 +1,8 @@
1
- import { StorageCopySource, StorageCopyDestination } from './Storage';
1
+ import {
2
+ StorageCopySource,
3
+ StorageCopyDestination,
4
+ StorageCopyConfig,
5
+ } from './Storage';
2
6
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
7
  // SPDX-License-Identifier: Apache-2.0
4
8
  export interface StorageProvider {
@@ -20,6 +24,9 @@ export interface StorageProvider {
20
24
  // get object/pre-signed url from storage
21
25
  get(key: string, options?): Promise<string | Object>;
22
26
 
27
+ // get properties of object
28
+ getProperties?(key: string, options?): Promise<Object>;
29
+
23
30
  // upload storage object
24
31
  put(key: string, object, options?): Promise<Object> | UploadTask;
25
32
 
@@ -52,4 +59,14 @@ export interface StorageProviderWithCopy extends StorageProvider {
52
59
  ): Promise<any>;
53
60
  }
54
61
 
55
- export type StorageProviderApi = 'copy' | 'get' | 'put' | 'remove' | 'list';
62
+ export interface StorageProviderWithGetProperties extends StorageProvider {
63
+ getProperties(key: string, options?): Promise<Object>;
64
+ }
65
+
66
+ export type StorageProviderApi =
67
+ | 'copy'
68
+ | 'get'
69
+ | 'put'
70
+ | 'remove'
71
+ | 'list'
72
+ | 'getProperties';
@@ -15,6 +15,8 @@ import {
15
15
  S3ProviderListOutput,
16
16
  S3ProviderCopyOutput,
17
17
  S3ProviderPutOutput,
18
+ S3ProviderGetPropertiesOutput,
19
+ StorageProviderWithGetProperties,
18
20
  } from '../';
19
21
 
20
22
  type Tail<T extends any[]> = ((...t: T) => void) extends (
@@ -69,13 +71,26 @@ export type StorageCopyDestination = Omit<StorageCopyTarget, 'identityId'>;
69
71
  * config.
70
72
  */
71
73
  type StorageOperationConfig<
72
- T extends StorageProvider | StorageProviderWithCopy,
74
+ T extends
75
+ | StorageProvider
76
+ | StorageProviderWithCopy
77
+ | StorageProviderWithGetProperties,
73
78
  U extends StorageProviderApi
74
79
  > = ReturnType<T['getProviderName']> extends 'AWSS3'
75
80
  ? LastParameter<AWSS3Provider[U]> // check if it has 'copy' function because 'copy' is optional
81
+ : T extends StorageProviderWithGetProperties & StorageProviderWithCopy
82
+ ? LastParameter<T[U]> & {
83
+ provider: ReturnType<T['getProviderName']>;
84
+ }
76
85
  : T extends StorageProviderWithCopy
77
- ? LastParameter<T[U]> & { provider: ReturnType<T['getProviderName']> }
78
- : LastParameter<T[Exclude<U, 'copy'>]> & {
86
+ ? LastParameter<T[Exclude<U, 'getProperties'>]> & {
87
+ provider: ReturnType<T['getProviderName']>;
88
+ }
89
+ : T extends StorageProviderWithGetProperties
90
+ ? LastParameter<T[Exclude<U, 'copy'>]> & {
91
+ provider: ReturnType<T['getProviderName']>;
92
+ }
93
+ : LastParameter<T[Exclude<U, 'copy' | 'getProperties'>]> & {
79
94
  provider: ReturnType<T['getProviderName']>;
80
95
  };
81
96
 
@@ -87,6 +102,14 @@ export type StorageGetConfig<T extends Record<string, any>> =
87
102
  T
88
103
  >;
89
104
 
105
+ export type StorageGetPropertiesConfig<T extends Record<string, any>> =
106
+ T extends StorageProviderWithGetProperties
107
+ ? StorageOperationConfig<T, 'getProperties'>
108
+ : StorageOperationConfigMap<
109
+ StorageOperationConfig<AWSS3Provider, 'getProperties'>,
110
+ T
111
+ >;
112
+
90
113
  export type StoragePutConfig<T extends Record<string, any>> =
91
114
  T extends StorageProvider
92
115
  ? StorageOperationConfig<T, 'put'>
@@ -132,9 +155,13 @@ type PickProviderOutput<
132
155
  > = T extends StorageProvider
133
156
  ? T['getProviderName'] extends 'AWSS3'
134
157
  ? DefaultOutput
135
- : T extends StorageProviderWithCopy
158
+ : T extends StorageProviderWithCopy & StorageProviderWithGetProperties
136
159
  ? ReturnType<T[api]>
137
- : ReturnType<T[Exclude<api, 'copy'>]>
160
+ : T extends StorageProviderWithCopy
161
+ ? ReturnType<T[Exclude<api, 'getProperties'>]>
162
+ : T extends StorageProviderWithGetProperties
163
+ ? ReturnType<T[Exclude<api, 'copy'>]>
164
+ : ReturnType<T[Exclude<api, 'copy' | 'getProperties'>]>
138
165
  : T extends { provider: string }
139
166
  ? T extends { provider: 'AWSS3' }
140
167
  ? DefaultOutput
@@ -168,6 +195,12 @@ export type StorageCopyOutput<T> = PickProviderOutput<
168
195
  'copy'
169
196
  >;
170
197
 
198
+ export type StorageGetPropertiesOutput<T> = PickProviderOutput<
199
+ Promise<S3ProviderGetPropertiesOutput>,
200
+ T,
201
+ 'getProperties'
202
+ >;
203
+
171
204
  /**
172
205
  * Utility type to allow custom provider to use any config keys, if provider is set to AWSS3 then it should use
173
206
  * AWSS3Provider's config.