diff --git a/packages/storage/__tests__/providers/s3/apis/copy.test.ts b/packages/storage/__tests__/providers/s3/apis/copy.test.ts index c7896ca868e..52eaf7c902f 100644 --- a/packages/storage/__tests__/providers/s3/apis/copy.test.ts +++ b/packages/storage/__tests__/providers/s3/apis/copy.test.ts @@ -2,14 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 import { AWSCredentials } from '@aws-amplify/core/internals/utils'; -import { Amplify } from '@aws-amplify/core'; +import { Amplify, StorageAccessLevel } from '@aws-amplify/core'; import { StorageError } from '../../../../src/errors/StorageError'; import { StorageValidationErrorCode } from '../../../../src/errors/types/validation'; import { copyObject } from '../../../../src/providers/s3/utils/client'; import { copy } from '../../../../src/providers/s3/apis'; import { - CopySourceOptionsWithKey, - CopyDestinationOptionsWithKey, + CopyInput, + CopyWithPathInput, + CopyOutput, + CopyWithPathOutput, } from '../../../../src/providers/s3/types'; jest.mock('../../../../src/providers/s3/utils/client'); @@ -67,6 +69,8 @@ describe('copy API', () => { describe('Happy Cases', () => { describe('With key', () => { + const copyWrapper = async (input: CopyInput): Promise => + copy(input); beforeEach(() => { mockCopyObject.mockImplementation(() => { return { @@ -77,7 +81,14 @@ describe('copy API', () => { afterEach(() => { jest.clearAllMocks(); }); - [ + const testCases: Array<{ + source: { accessLevel?: StorageAccessLevel; targetIdentityId?: string }; + destination: { + accessLevel?: StorageAccessLevel; + }; + expectedSourceKey: string; + expectedDestinationKey: string; + }> = [ { source: { accessLevel: 'guest' }, destination: { accessLevel: 'guest' }, @@ -150,7 +161,8 @@ describe('copy API', () => { expectedSourceKey: `${bucket}/protected/${targetIdentityId}/${sourceKey}`, expectedDestinationKey: `protected/${defaultIdentityId}/${destinationKey}`, }, - ].forEach( + ]; + testCases.forEach( ({ source, destination, @@ -160,24 +172,18 @@ describe('copy API', () => { const targetIdentityIdMsg = source?.targetIdentityId ? `with targetIdentityId` : ''; - const copyResult = { - key: destinationKey, - path: expectedDestinationKey, - }; - it(`should copy ${source.accessLevel} ${targetIdentityIdMsg} -> ${destination.accessLevel}`, async () => { - expect( - await copy({ - source: { - ...(source as CopySourceOptionsWithKey), - key: sourceKey, - }, - destination: { - ...(destination as CopyDestinationOptionsWithKey), - key: destinationKey, - }, - }), - ).toEqual(copyResult); + const { key } = await copyWrapper({ + source: { + ...source, + key: sourceKey, + }, + destination: { + ...destination, + key: destinationKey, + }, + }); + expect(key).toEqual(destinationKey); expect(copyObject).toHaveBeenCalledTimes(1); expect(copyObject).toHaveBeenCalledWith(copyObjectClientConfig, { ...copyObjectClientBaseParams, @@ -190,6 +196,10 @@ describe('copy API', () => { }); describe('With path', () => { + const copyWrapper = async ( + input: CopyWithPathInput, + ): Promise => copy(input); + beforeEach(() => { mockCopyObject.mockImplementation(() => { return { @@ -222,15 +232,11 @@ describe('copy API', () => { destinationPath, expectedDestinationPath, }) => { - expect( - await copy({ - source: { path: sourcePath }, - destination: { path: destinationPath }, - }), - ).toEqual({ - path: expectedDestinationPath, - key: expectedDestinationPath, + const { path } = await copyWrapper({ + source: { path: sourcePath }, + destination: { path: destinationPath }, }); + expect(path).toEqual(expectedDestinationPath); expect(copyObject).toHaveBeenCalledTimes(1); expect(copyObject).toHaveBeenCalledWith(copyObjectClientConfig, { ...copyObjectClientBaseParams, diff --git a/packages/storage/__tests__/providers/s3/apis/downloadData.test.ts b/packages/storage/__tests__/providers/s3/apis/downloadData.test.ts index 8118d47e56a..0c9c4a3d007 100644 --- a/packages/storage/__tests__/providers/s3/apis/downloadData.test.ts +++ b/packages/storage/__tests__/providers/s3/apis/downloadData.test.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { AWSCredentials } from '@aws-amplify/core/internals/utils'; -import { Amplify } from '@aws-amplify/core'; +import { Amplify, StorageAccessLevel } from '@aws-amplify/core'; import { getObject } from '../../../../src/providers/s3/utils/client'; import { downloadData } from '../../../../src/providers/s3'; import { @@ -10,13 +10,18 @@ import { validateStorageOperationInput, } from '../../../../src/providers/s3/utils'; import { - DownloadDataOptionsWithKey, - DownloadDataOptionsWithPath, + DownloadDataInput, + DownloadDataWithPathInput, } from '../../../../src/providers/s3/types'; import { STORAGE_INPUT_KEY, STORAGE_INPUT_PATH, } from '../../../../src/providers/s3/utils/constants'; +import { StorageDownloadDataOutput } from '../../../../src/types'; +import { + ItemWithKey, + ItemWithPath, +} from '../../../../src/providers/s3/types/outputs'; jest.mock('../../../../src/providers/s3/utils/client'); jest.mock('../../../../src/providers/s3/utils'); @@ -36,11 +41,21 @@ const credentials: AWSCredentials = { sessionToken: 'sessionToken', secretAccessKey: 'secretAccessKey', }; -const key = 'key'; +const inputKey = 'key'; +const inputPath = 'path'; const bucket = 'bucket'; const region = 'region'; const targetIdentityId = 'targetIdentityId'; const defaultIdentityId = 'defaultIdentityId'; +const mockDownloadResultBase = { + body: 'body', + lastModified: 'lastModified', + size: 'contentLength', + eTag: 'eTag', + metadata: 'metadata', + versionId: 'versionId', + contentType: 'contentType', +}; const mockFetchAuthSession = Amplify.Auth.fetchAuthSession as jest.Mock; const mockCreateDownloadTask = createDownloadTask as jest.Mock; @@ -62,55 +77,69 @@ describe('downloadData with key', () => { }, }); }); - mockCreateDownloadTask.mockReturnValue('downloadTask'); - mockValidateStorageInput.mockReturnValue({ - inputType: STORAGE_INPUT_KEY, - objectKey: key, - }); beforeEach(() => { jest.clearAllMocks(); + + mockCreateDownloadTask.mockReturnValue('downloadTask'); + mockValidateStorageInput.mockReturnValue({ + inputType: STORAGE_INPUT_KEY, + objectKey: inputKey, + }); }); it('should return a download task with key', async () => { - expect(downloadData({ key: 'key' })).toBe('downloadTask'); + const mockDownloadInput: DownloadDataInput = { + key: inputKey, + options: { accessLevel: 'protected', targetIdentityId: targetIdentityId }, + }; + expect(downloadData(mockDownloadInput)).toBe('downloadTask'); }); - test.each([ + const testCases: Array<{ + expectedKey: string; + options?: { accessLevel?: StorageAccessLevel; targetIdentityId?: string }; + }> = [ { - expectedKey: `public/${key}`, + expectedKey: `public/${inputKey}`, }, { options: { accessLevel: 'guest' }, - expectedKey: `public/${key}`, + expectedKey: `public/${inputKey}`, }, { options: { accessLevel: 'private' }, - expectedKey: `private/${defaultIdentityId}/${key}`, + expectedKey: `private/${defaultIdentityId}/${inputKey}`, }, { options: { accessLevel: 'protected' }, - expectedKey: `protected/${defaultIdentityId}/${key}`, + expectedKey: `protected/${defaultIdentityId}/${inputKey}`, }, { options: { accessLevel: 'protected', targetIdentityId }, - expectedKey: `protected/${targetIdentityId}/${key}`, + expectedKey: `protected/${targetIdentityId}/${inputKey}`, }, - ])( + ]; + + test.each(testCases)( 'should supply the correct parameters to getObject API handler with $expectedKey accessLevel', async ({ options, expectedKey }) => { (getObject as jest.Mock).mockResolvedValueOnce({ Body: 'body' }); const onProgress = jest.fn(); downloadData({ - key, + key: inputKey, options: { ...options, useAccelerateEndpoint: true, onProgress, - } as DownloadDataOptionsWithKey, + }, }); const job = mockCreateDownloadTask.mock.calls[0][0].job; - await job(); + const { key, body }: StorageDownloadDataOutput = await job(); + expect({ key, body }).toEqual({ + key: inputKey, + body: 'body', + }); expect(getObject).toHaveBeenCalledTimes(1); expect(getObject).toHaveBeenCalledWith( { @@ -130,38 +159,40 @@ describe('downloadData with key', () => { ); it('should assign the getObject API handler response to the result with key', async () => { - const lastModified = 'lastModified'; - const contentLength = 'contentLength'; - const eTag = 'eTag'; - const metadata = 'metadata'; - const versionId = 'versionId'; - const contentType = 'contentType'; - const body = 'body'; - const key = 'key'; - const expectedKey = `public/${key}`; (getObject as jest.Mock).mockResolvedValueOnce({ - Body: body, - LastModified: lastModified, - ContentLength: contentLength, - ETag: eTag, - Metadata: metadata, - VersionId: versionId, - ContentType: contentType, + Body: 'body', + LastModified: 'lastModified', + ContentLength: 'contentLength', + ETag: 'eTag', + Metadata: 'metadata', + VersionId: 'versionId', + ContentType: 'contentType', }); - downloadData({ key }); + downloadData({ key: inputKey }); const job = mockCreateDownloadTask.mock.calls[0][0].job; - const result = await job(); - expect(getObject).toHaveBeenCalledTimes(1); - expect(result).toEqual({ + const { key, - path: `public/${key}`, body, - lastModified, - size: contentLength, + contentType, eTag, + lastModified, metadata, + size, versionId, + }: StorageDownloadDataOutput = await job(); + expect(getObject).toHaveBeenCalledTimes(1); + expect({ + key, + body, contentType, + eTag, + lastModified, + metadata, + size, + versionId, + }).toEqual({ + key: inputKey, + ...mockDownloadResultBase, }); }); @@ -171,7 +202,7 @@ describe('downloadData with key', () => { (getObject as jest.Mock).mockResolvedValueOnce({ Body: 'body' }); downloadData({ - key: 'mockKey', + key: inputKey, options: { bytesRange: { start, end }, }, @@ -206,7 +237,7 @@ describe('downloadData with path', () => { mockCreateDownloadTask.mockReturnValue('downloadTask'); mockValidateStorageInput.mockReturnValue({ inputType: STORAGE_INPUT_PATH, - objectKey: 'path', + objectKey: inputPath, }); }); @@ -215,17 +246,21 @@ describe('downloadData with path', () => { }); it('should return a download task with path', async () => { - expect(downloadData({ path: 'path' })).toBe('downloadTask'); + const mockDownloadInput: DownloadDataWithPathInput = { + path: inputPath, + options: { useAccelerateEndpoint: true }, + }; + expect(downloadData(mockDownloadInput)).toBe('downloadTask'); }); test.each([ { - path: 'path', - expectedKey: 'path', + path: inputPath, + expectedKey: inputPath, }, { - path: () => 'path', - expectedKey: 'path', + path: () => inputPath, + expectedKey: inputPath, }, ])( 'should call getObject API with $expectedKey when path provided is $path', @@ -233,14 +268,24 @@ describe('downloadData with path', () => { (getObject as jest.Mock).mockResolvedValueOnce({ Body: 'body' }); const onProgress = jest.fn(); downloadData({ - path: path, + path, options: { useAccelerateEndpoint: true, onProgress, - } as DownloadDataOptionsWithPath, + }, }); const job = mockCreateDownloadTask.mock.calls[0][0].job; - await job(); + const { + path: resultPath, + body, + }: StorageDownloadDataOutput = await job(); + expect({ + path: resultPath, + body, + }).toEqual({ + path: expectedKey, + body: 'body', + }); expect(getObject).toHaveBeenCalledTimes(1); expect(getObject).toHaveBeenCalledWith( { @@ -260,37 +305,40 @@ describe('downloadData with path', () => { ); it('should assign the getObject API handler response to the result with path', async () => { - const lastModified = 'lastModified'; - const contentLength = 'contentLength'; - const eTag = 'eTag'; - const metadata = 'metadata'; - const versionId = 'versionId'; - const contentType = 'contentType'; - const body = 'body'; - const path = 'path'; (getObject as jest.Mock).mockResolvedValueOnce({ - Body: body, - LastModified: lastModified, - ContentLength: contentLength, - ETag: eTag, - Metadata: metadata, - VersionId: versionId, - ContentType: contentType, + Body: 'body', + LastModified: 'lastModified', + ContentLength: 'contentLength', + ETag: 'eTag', + Metadata: 'metadata', + VersionId: 'versionId', + ContentType: 'contentType', }); - downloadData({ path }); + downloadData({ path: inputPath }); const job = mockCreateDownloadTask.mock.calls[0][0].job; - const result = await job(); - expect(getObject).toHaveBeenCalledTimes(1); - expect(result).toEqual({ + const { path, - key: path, body, - lastModified, - size: contentLength, + contentType, eTag, + lastModified, metadata, + size, versionId, + }: StorageDownloadDataOutput = await job(); + expect(getObject).toHaveBeenCalledTimes(1); + expect({ + path, + body, contentType, + eTag, + lastModified, + metadata, + size, + versionId, + }).toEqual({ + path: inputPath, + ...mockDownloadResultBase, }); }); @@ -300,7 +348,7 @@ describe('downloadData with path', () => { (getObject as jest.Mock).mockResolvedValueOnce({ Body: 'body' }); downloadData({ - path: 'mockPath', + path: inputPath, options: { bytesRange: { start, end }, }, diff --git a/packages/storage/__tests__/providers/s3/apis/getProperties.test.ts b/packages/storage/__tests__/providers/s3/apis/getProperties.test.ts index a8731fb65c5..191802f04f9 100644 --- a/packages/storage/__tests__/providers/s3/apis/getProperties.test.ts +++ b/packages/storage/__tests__/providers/s3/apis/getProperties.test.ts @@ -4,10 +4,12 @@ import { headObject } from '../../../../src/providers/s3/utils/client'; import { getProperties } from '../../../../src/providers/s3'; import { AWSCredentials } from '@aws-amplify/core/internals/utils'; -import { Amplify } from '@aws-amplify/core'; +import { Amplify, StorageAccessLevel } from '@aws-amplify/core'; import { - GetPropertiesOptionsWithKey, - GetPropertiesOptionsWithPath, + GetPropertiesInput, + GetPropertiesWithPathInput, + GetPropertiesOutput, + GetPropertiesWithPathOutput, } from '../../../../src/providers/s3/types'; jest.mock('../../../../src/providers/s3/utils/client'); @@ -22,7 +24,7 @@ jest.mock('@aws-amplify/core', () => ({ }, }, })); -const mockHeadObject = headObject as jest.Mock; +const mockHeadObject = headObject as jest.MockedFunction; const mockFetchAuthSession = Amplify.Auth.fetchAuthSession as jest.Mock; const mockGetConfig = Amplify.getConfig as jest.Mock; @@ -33,12 +35,24 @@ const credentials: AWSCredentials = { sessionToken: 'sessionToken', secretAccessKey: 'secretAccessKey', }; -const key = 'key'; -const path = 'path'; +const inputKey = 'key'; +const inputPath = 'path'; const targetIdentityId = 'targetIdentityId'; const defaultIdentityId = 'defaultIdentityId'; +const expectedResult = { + size: 100, + contentType: 'text/plain', + eTag: 'etag', + metadata: { key: 'value' }, + lastModified: new Date('01-01-1980'), + versionId: 'version-id', +}; + describe('getProperties with key', () => { + const getPropertiesWrapper = ( + input: GetPropertiesInput, + ): Promise => getProperties(input); beforeAll(() => { mockFetchAuthSession.mockResolvedValue({ credentials, @@ -54,66 +68,81 @@ describe('getProperties with key', () => { }); }); describe('Happy cases: With key', () => { - const expected = { - key, - size: '100', - contentType: 'text/plain', - eTag: 'etag', - metadata: { key: 'value' }, - lastModified: 'last-modified', - versionId: 'version-id', - }; const config = { credentials, region: 'region', userAgentValue: expect.any(String), }; beforeEach(() => { - mockHeadObject.mockReturnValueOnce({ - ContentLength: '100', + mockHeadObject.mockResolvedValue({ + ContentLength: 100, ContentType: 'text/plain', ETag: 'etag', - LastModified: 'last-modified', + LastModified: new Date('01-01-1980'), Metadata: { key: 'value' }, VersionId: 'version-id', + $metadata: {} as any, }); }); afterEach(() => { jest.clearAllMocks(); }); - test.each([ + + const testCases: Array<{ + expectedKey: string; + options?: { accessLevel?: StorageAccessLevel; targetIdentityId?: string }; + }> = [ { - expectedKey: `public/${key}`, + expectedKey: `public/${inputKey}`, }, { options: { accessLevel: 'guest' }, - expectedKey: `public/${key}`, + expectedKey: `public/${inputKey}`, }, { options: { accessLevel: 'private' }, - expectedKey: `private/${defaultIdentityId}/${key}`, + expectedKey: `private/${defaultIdentityId}/${inputKey}`, }, { options: { accessLevel: 'protected' }, - expectedKey: `protected/${defaultIdentityId}/${key}`, + expectedKey: `protected/${defaultIdentityId}/${inputKey}`, }, { options: { accessLevel: 'protected', targetIdentityId }, - expectedKey: `protected/${targetIdentityId}/${key}`, + expectedKey: `protected/${targetIdentityId}/${inputKey}`, }, - ])( + ]; + test.each(testCases)( 'should getProperties with key $expectedKey', async ({ options, expectedKey }) => { const headObjectOptions = { Bucket: 'bucket', Key: expectedKey, }; - expect( - await getProperties({ - key, - options: options as GetPropertiesOptionsWithKey, - }), - ).toEqual({ ...expected, path: expectedKey }); + const { + key, + contentType, + eTag, + lastModified, + metadata, + size, + versionId, + } = await getPropertiesWrapper({ + key: inputKey, + options, + }); + expect({ + key, + contentType, + eTag, + lastModified, + metadata, + size, + versionId, + }).toEqual({ + key: inputKey, + ...expectedResult, + }); expect(headObject).toHaveBeenCalledTimes(1); expect(headObject).toHaveBeenCalledWith(config, headObjectOptions); }, @@ -133,7 +162,7 @@ describe('getProperties with key', () => { ); expect.assertions(3); try { - await getProperties({ key }); + await getPropertiesWrapper({ key: inputKey }); } catch (error: any) { expect(headObject).toHaveBeenCalledTimes(1); expect(headObject).toHaveBeenCalledWith( @@ -144,7 +173,7 @@ describe('getProperties with key', () => { }, { Bucket: 'bucket', - Key: `public/${key}`, + Key: `public/${inputKey}`, }, ); expect(error.$metadata.httpStatusCode).toBe(404); @@ -154,6 +183,9 @@ describe('getProperties with key', () => { }); describe('Happy cases: With path', () => { + const getPropertiesWrapper = ( + input: GetPropertiesWithPathInput, + ): Promise => getProperties(input); beforeAll(() => { mockFetchAuthSession.mockResolvedValue({ credentials, @@ -169,16 +201,6 @@ describe('Happy cases: With path', () => { }); }); describe('getProperties with path', () => { - const expected = { - key: path, - path, - size: '100', - contentType: 'text/plain', - eTag: 'etag', - metadata: { key: 'value' }, - lastModified: 'last-modified', - versionId: 'version-id', - }; const config = { credentials, region: 'region', @@ -186,13 +208,14 @@ describe('Happy cases: With path', () => { userAgentValue: expect.any(String), }; beforeEach(() => { - mockHeadObject.mockReturnValueOnce({ - ContentLength: '100', + mockHeadObject.mockResolvedValue({ + ContentLength: 100, ContentType: 'text/plain', ETag: 'etag', - LastModified: 'last-modified', + LastModified: new Date('01-01-1980'), Metadata: { key: 'value' }, VersionId: 'version-id', + $metadata: {} as any, }); }); afterEach(() => { @@ -200,28 +223,46 @@ describe('Happy cases: With path', () => { }); test.each([ { - testPath: path, - expectedKey: path, + testPath: inputPath, + expectedPath: inputPath, }, { - testPath: () => path, - expectedKey: path, + testPath: () => inputPath, + expectedPath: inputPath, }, ])( - 'should getProperties with path $path and expectedKey $expectedKey', - async ({ testPath, expectedKey }) => { + 'should getProperties with path $path and expectedPath $expectedPath', + async ({ testPath, expectedPath }) => { const headObjectOptions = { Bucket: 'bucket', - Key: expectedKey, + Key: expectedPath, }; - expect( - await getProperties({ - path: testPath, - options: { - useAccelerateEndpoint: true, - } as GetPropertiesOptionsWithPath, - }), - ).toEqual(expected); + const { + path, + contentType, + eTag, + lastModified, + metadata, + size, + versionId, + } = await getPropertiesWrapper({ + path: testPath, + options: { + useAccelerateEndpoint: true, + }, + }); + expect({ + path, + contentType, + eTag, + lastModified, + metadata, + size, + versionId, + }).toEqual({ + path: expectedPath, + ...expectedResult, + }); expect(headObject).toHaveBeenCalledTimes(1); expect(headObject).toHaveBeenCalledWith(config, headObjectOptions); }, @@ -241,7 +282,7 @@ describe('Happy cases: With path', () => { ); expect.assertions(3); try { - await getProperties({ path }); + await getPropertiesWrapper({ path: inputPath }); } catch (error: any) { expect(headObject).toHaveBeenCalledTimes(1); expect(headObject).toHaveBeenCalledWith( @@ -252,7 +293,7 @@ describe('Happy cases: With path', () => { }, { Bucket: 'bucket', - Key: path, + Key: inputPath, }, ); expect(error.$metadata.httpStatusCode).toBe(404); diff --git a/packages/storage/__tests__/providers/s3/apis/getUrl.test.ts b/packages/storage/__tests__/providers/s3/apis/getUrl.test.ts index a68c8c3516b..8f56299d943 100644 --- a/packages/storage/__tests__/providers/s3/apis/getUrl.test.ts +++ b/packages/storage/__tests__/providers/s3/apis/getUrl.test.ts @@ -3,14 +3,16 @@ import { getUrl } from '../../../../src/providers/s3/apis'; import { AWSCredentials } from '@aws-amplify/core/internals/utils'; -import { Amplify } from '@aws-amplify/core'; +import { Amplify, StorageAccessLevel } from '@aws-amplify/core'; import { getPresignedGetObjectUrl, headObject, } from '../../../../src/providers/s3/utils/client'; import { - GetUrlOptionsWithKey, - GetUrlOptionsWithPath, + GetUrlInput, + GetUrlWithPathInput, + GetUrlOutput, + GetUrlWithPathOutput, } from '../../../../src/providers/s3/types'; jest.mock('../../../../src/providers/s3/utils/client'); @@ -37,8 +39,11 @@ const credentials: AWSCredentials = { }; const targetIdentityId = 'targetIdentityId'; const defaultIdentityId = 'defaultIdentityId'; +const mockURL = new URL('https://google.com'); describe('getUrl test with key', () => { + const getUrlWrapper = (input: GetUrlInput): Promise => + getUrl(input); beforeAll(() => { mockFetchAuthSession.mockResolvedValue({ credentials, @@ -62,24 +67,28 @@ describe('getUrl test with key', () => { }; const key = 'key'; beforeEach(() => { - (headObject as jest.Mock).mockImplementation(() => { - return { - Key: 'key', - ContentLength: '100', - ContentType: 'text/plain', - ETag: 'etag', - LastModified: 'last-modified', - Metadata: { key: 'value' }, - }; - }); - (getPresignedGetObjectUrl as jest.Mock).mockReturnValueOnce({ - url: new URL('https://google.com'), + (headObject as jest.MockedFunction).mockResolvedValue({ + ContentLength: 100, + ContentType: 'text/plain', + ETag: 'etag', + LastModified: new Date('01-01-1980'), + Metadata: { meta: 'value' }, + $metadata: {} as any, }); + ( + getPresignedGetObjectUrl as jest.MockedFunction< + typeof getPresignedGetObjectUrl + > + ).mockResolvedValue(mockURL); }); afterEach(() => { jest.clearAllMocks(); }); - test.each([ + + const testCases: Array<{ + options?: { accessLevel?: StorageAccessLevel; targetIdentityId?: string }; + expectedKey: string; + }> = [ { expectedKey: `public/${key}`, }, @@ -99,26 +108,30 @@ describe('getUrl test with key', () => { options: { accessLevel: 'protected', targetIdentityId }, expectedKey: `protected/${targetIdentityId}/${key}`, }, - ])( + ]; + + test.each(testCases)( 'should getUrl with key $expectedKey', async ({ options, expectedKey }) => { const headObjectOptions = { Bucket: bucket, Key: expectedKey, }; - const result = await getUrl({ + const { url, expiresAt } = await getUrlWrapper({ key, options: { ...options, validateObjectExistence: true, - } as GetUrlOptionsWithKey, + }, }); + const expectedResult = { + url: mockURL, + expiresAt: expect.any(Date), + }; expect(getPresignedGetObjectUrl).toHaveBeenCalledTimes(1); expect(headObject).toHaveBeenCalledTimes(1); expect(headObject).toHaveBeenCalledWith(config, headObjectOptions); - expect(result.url).toEqual({ - url: new URL('https://google.com'), - }); + expect({ url, expiresAt }).toEqual(expectedResult); }, ); }); @@ -135,7 +148,7 @@ describe('getUrl test with key', () => { }); expect.assertions(2); try { - await getUrl({ + await getUrlWrapper({ key: 'invalid_key', options: { validateObjectExistence: true }, }); @@ -148,6 +161,9 @@ describe('getUrl test with key', () => { }); describe('getUrl test with path', () => { + const getUrlWrapper = ( + input: GetUrlWithPathInput, + ): Promise => getUrl(input); beforeAll(() => { mockFetchAuthSession.mockResolvedValue({ credentials, @@ -170,19 +186,19 @@ describe('getUrl test with path', () => { userAgentValue: expect.any(String), }; beforeEach(() => { - (headObject as jest.Mock).mockImplementation(() => { - return { - Key: 'path', - ContentLength: '100', - ContentType: 'text/plain', - ETag: 'etag', - LastModified: 'last-modified', - Metadata: { key: 'value' }, - }; - }); - (getPresignedGetObjectUrl as jest.Mock).mockReturnValueOnce({ - url: new URL('https://google.com'), + (headObject as jest.MockedFunction).mockResolvedValue({ + ContentLength: 100, + ContentType: 'text/plain', + ETag: 'etag', + LastModified: new Date('01-01-1980'), + Metadata: { meta: 'value' }, + $metadata: {} as any, }); + ( + getPresignedGetObjectUrl as jest.MockedFunction< + typeof getPresignedGetObjectUrl + > + ).mockResolvedValue(mockURL); }); afterEach(() => { jest.clearAllMocks(); @@ -204,17 +220,18 @@ describe('getUrl test with path', () => { Bucket: bucket, Key: expectedKey, }; - const result = await getUrl({ + const { url, expiresAt } = await getUrlWrapper({ path, options: { validateObjectExistence: true, - } as GetUrlOptionsWithPath, + }, }); expect(getPresignedGetObjectUrl).toHaveBeenCalledTimes(1); expect(headObject).toHaveBeenCalledTimes(1); expect(headObject).toHaveBeenCalledWith(config, headObjectOptions); - expect(result.url).toEqual({ - url: new URL('https://google.com'), + expect({ url, expiresAt }).toEqual({ + url: mockURL, + expiresAt: expect.any(Date), }); }, ); @@ -232,7 +249,7 @@ describe('getUrl test with path', () => { }); expect.assertions(2); try { - await getUrl({ + await getUrlWrapper({ path: 'invalid_key', options: { validateObjectExistence: true }, }); diff --git a/packages/storage/__tests__/providers/s3/apis/list.test.ts b/packages/storage/__tests__/providers/s3/apis/list.test.ts index 0b46c6a21db..21ad76cdc33 100644 --- a/packages/storage/__tests__/providers/s3/apis/list.test.ts +++ b/packages/storage/__tests__/providers/s3/apis/list.test.ts @@ -2,13 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 import { AWSCredentials } from '@aws-amplify/core/internals/utils'; -import { Amplify } from '@aws-amplify/core'; +import { Amplify, StorageAccessLevel } from '@aws-amplify/core'; import { listObjectsV2 } from '../../../../src/providers/s3/utils/client'; import { list } from '../../../../src/providers/s3'; import { - ListAllOptionsWithPrefix, - ListPaginateOptionsWithPrefix, + ListAllInput, + ListAllWithPathInput, + ListAllOutput, + ListAllWithPathOutput, + ListPaginateInput, + ListPaginateWithPathInput, ListPaginateOutput, + ListPaginateWithPathOutput, } from '../../../../src/providers/s3/types'; jest.mock('../../../../src/providers/s3/utils/client'); @@ -90,46 +95,58 @@ describe('list API', () => { }); }); describe('Prefix: Happy Cases:', () => { + const listAllWrapper = (input: ListAllInput): Promise => + list(input); + const listPaginatedWrapper = ( + input: ListPaginateInput, + ): Promise => list(input); afterEach(() => { jest.clearAllMocks(); }); - const accessLevelTests = [ + const accessLevelTests: Array<{ + prefix?: string; + expectedKey: string; + options?: { + accessLevel?: StorageAccessLevel; + targetIdentityId?: string; + }; + }> = [ { - expectedPath: `public/`, + expectedKey: `public/`, }, { options: { accessLevel: 'guest' }, - expectedPath: `public/`, + expectedKey: `public/`, }, { - key, - expectedPath: `public/${key}`, + prefix: key, + expectedKey: `public/${key}`, }, { - key, + prefix: key, options: { accessLevel: 'guest' }, - expectedPath: `public/${key}`, + expectedKey: `public/${key}`, }, { - key, + prefix: key, options: { accessLevel: 'private' }, - expectedPath: `private/${defaultIdentityId}/${key}`, + expectedKey: `private/${defaultIdentityId}/${key}`, }, { - key, + prefix: key, options: { accessLevel: 'protected' }, - expectedPath: `protected/${defaultIdentityId}/${key}`, + expectedKey: `protected/${defaultIdentityId}/${key}`, }, { - key, + prefix: key, options: { accessLevel: 'protected', targetIdentityId }, - expectedPath: `protected/${targetIdentityId}/${key}`, + expectedKey: `protected/${targetIdentityId}/${key}`, }, ]; - accessLevelTests.forEach(({ key, options, expectedPath }) => { - const pathMsg = key ? 'custom' : 'default'; + accessLevelTests.forEach(({ prefix, options, expectedKey }) => { + const pathMsg = prefix ? 'custom' : 'default'; const accessLevelMsg = options?.accessLevel ?? 'default'; const targetIdentityIdMsg = options?.targetIdentityId ? `with targetIdentityId` @@ -137,31 +154,32 @@ describe('list API', () => { it(`should list objects with pagination, default pageSize, ${pathMsg} path, ${accessLevelMsg} accessLevel ${targetIdentityIdMsg}`, async () => { mockListObject.mockImplementationOnce(() => { return { - Contents: [ - { ...listObjectClientBaseResultItem, Key: expectedPath }, - ], + Contents: [{ ...listObjectClientBaseResultItem, Key: expectedKey }], NextContinuationToken: nextToken, }; }); - let response = await list({ - prefix: key, - options: options as ListPaginateOptionsWithPrefix, + const response = await listPaginatedWrapper({ + prefix, + options: options, + }); + const { key, eTag, size, lastModified } = response.items[0]; + expect(response.items).toHaveLength(1); + expect({ key, eTag, size, lastModified }).toEqual({ + key: prefix ?? '', + ...listResultItem, }); - expect(response.items).toEqual([ - { ...listResultItem, key: key ?? '', path: expectedPath ?? '' }, - ]); expect(response.nextToken).toEqual(nextToken); expect(listObjectsV2).toHaveBeenCalledTimes(1); expect(listObjectsV2).toHaveBeenCalledWith(listObjectClientConfig, { Bucket: bucket, MaxKeys: 1000, - Prefix: expectedPath, + Prefix: expectedKey, }); }); }); - accessLevelTests.forEach(({ key, options, expectedPath }) => { - const pathMsg = key ? 'custom' : 'default'; + accessLevelTests.forEach(({ prefix, options, expectedKey }) => { + const pathMsg = prefix ? 'custom' : 'default'; const accessLevelMsg = options?.accessLevel ?? 'default'; const targetIdentityIdMsg = options?.targetIdentityId ? `with targetIdentityId` @@ -169,37 +187,38 @@ describe('list API', () => { it(`should list objects with pagination using pageSize, nextToken, ${pathMsg} path, ${accessLevelMsg} accessLevel ${targetIdentityIdMsg}`, async () => { mockListObject.mockImplementationOnce(() => { return { - Contents: [ - { ...listObjectClientBaseResultItem, Key: expectedPath }, - ], + Contents: [{ ...listObjectClientBaseResultItem, Key: expectedKey }], NextContinuationToken: nextToken, }; }); const customPageSize = 5; - const response: ListPaginateOutput = await list({ - prefix: key, + const response = await listPaginatedWrapper({ + prefix, options: { - ...(options as ListPaginateOptionsWithPrefix), + ...options, pageSize: customPageSize, nextToken: nextToken, }, }); - expect(response.items).toEqual([ - { ...listResultItem, key: key ?? '', path: expectedPath ?? '' }, - ]); + const { key, eTag, size, lastModified } = response.items[0]; + expect(response.items).toHaveLength(1); + expect({ key, eTag, size, lastModified }).toEqual({ + key: prefix ?? '', + ...listResultItem, + }); expect(response.nextToken).toEqual(nextToken); expect(listObjectsV2).toHaveBeenCalledTimes(1); expect(listObjectsV2).toHaveBeenCalledWith(listObjectClientConfig, { Bucket: bucket, - Prefix: expectedPath, + Prefix: expectedKey, ContinuationToken: nextToken, MaxKeys: customPageSize, }); }); }); - accessLevelTests.forEach(({ key, options, expectedPath }) => { - const pathMsg = key ? 'custom' : 'default'; + accessLevelTests.forEach(({ prefix, options, expectedKey }) => { + const pathMsg = prefix ? 'custom' : 'default'; const accessLevelMsg = options?.accessLevel ?? 'default'; const targetIdentityIdMsg = options?.targetIdentityId ? `with targetIdentityId` @@ -208,9 +227,9 @@ describe('list API', () => { mockListObject.mockImplementationOnce(() => { return {}; }); - let response = await list({ - prefix: key, - options: options as ListPaginateOptionsWithPrefix, + let response = await listPaginatedWrapper({ + prefix, + options, }); expect(response.items).toEqual([]); @@ -218,30 +237,29 @@ describe('list API', () => { expect(listObjectsV2).toHaveBeenCalledWith(listObjectClientConfig, { Bucket: bucket, MaxKeys: 1000, - Prefix: expectedPath, + Prefix: expectedKey, }); }); }); - accessLevelTests.forEach(({ key, options, expectedPath }) => { - const pathMsg = key ? 'custom' : 'default'; + accessLevelTests.forEach(({ prefix: inputKey, options, expectedKey }) => { + const pathMsg = inputKey ? 'custom' : 'default'; const accessLevelMsg = options?.accessLevel ?? 'default'; const targetIdentityIdMsg = options?.targetIdentityId ? `with targetIdentityId` : ''; it(`should list all objects having three pages with ${pathMsg} path, ${accessLevelMsg} accessLevel ${targetIdentityIdMsg}`, async () => { mockListObjectsV2ApiWithPages(3); - const result = await list({ - prefix: key, - options: { ...options, listAll: true } as ListAllOptionsWithPrefix, + const result = await listAllWrapper({ + prefix: inputKey, + options: { ...options, listAll: true }, }); - - const listResult = { + const { key, eTag, lastModified, size } = result.items[0]; + expect(result.items).toHaveLength(3); + expect({ key, eTag, lastModified, size }).toEqual({ ...listResultItem, - key: key ?? '', - path: expectedPath ?? '', - }; - expect(result.items).toEqual([listResult, listResult, listResult]); + key: inputKey ?? '', + }); expect(result).not.toHaveProperty(nextToken); // listing three times for three pages @@ -253,7 +271,7 @@ describe('list API', () => { listObjectClientConfig, { Bucket: bucket, - Prefix: expectedPath, + Prefix: expectedKey, MaxKeys: 1000, ContinuationToken: undefined, }, @@ -264,7 +282,7 @@ describe('list API', () => { listObjectClientConfig, { Bucket: bucket, - Prefix: expectedPath, + Prefix: expectedKey, MaxKeys: 1000, ContinuationToken: nextToken, }, @@ -274,106 +292,110 @@ describe('list API', () => { }); describe('Path: Happy Cases:', () => { + const listAllWrapper = ( + input: ListAllWithPathInput, + ): Promise => list(input); + const listPaginatedWrapper = ( + input: ListPaginateWithPathInput, + ): Promise => list(input); const resolvePath = (path: string | Function) => typeof path === 'string' ? path : path({ identityId: defaultIdentityId }); afterEach(() => { jest.clearAllMocks(); mockListObject.mockClear(); }); - const pathAsFunctionAndStringTests = [ + const pathTestCases = [ { path: `public/${key}`, }, { - path: ({ identityId }: any) => `protected/${identityId}/${key}`, + path: ({ identityId }: { identityId: string }) => + `protected/${identityId}/${key}`, }, ]; - it.each(pathAsFunctionAndStringTests)( + it.each(pathTestCases)( 'should list objects with pagination, default pageSize, custom path', - async ({ path }) => { - const resolvedPath = resolvePath(path); + async ({ path: inputPath }) => { + const resolvedPath = resolvePath(inputPath); mockListObject.mockImplementationOnce(() => { return { Contents: [ { ...listObjectClientBaseResultItem, - Key: resolvedPath, + Key: resolvePath(inputPath), }, ], NextContinuationToken: nextToken, }; }); - let response = await list({ - path, + const response = await listPaginatedWrapper({ + path: resolvedPath, + }); + const { path, eTag, lastModified, size } = response.items[0]; + expect(response.items).toHaveLength(1); + expect({ path, eTag, lastModified, size }).toEqual({ + ...listResultItem, + path: resolvedPath, }); - expect(response.items).toEqual([ - { - ...listResultItem, - path: resolvedPath, - key: resolvedPath, - }, - ]); expect(response.nextToken).toEqual(nextToken); expect(listObjectsV2).toHaveBeenCalledTimes(1); expect(listObjectsV2).toHaveBeenCalledWith(listObjectClientConfig, { Bucket: bucket, MaxKeys: 1000, - Prefix: resolvedPath, + Prefix: resolvePath(inputPath), }); }, ); - it.each(pathAsFunctionAndStringTests)( + it.each(pathTestCases)( 'should list objects with pagination using custom pageSize, nextToken and custom path: ${path}', - async ({ path }) => { - const resolvedPath = resolvePath(path); + async ({ path: inputPath }) => { + const resolvedPath = resolvePath(inputPath); mockListObject.mockImplementationOnce(() => { return { Contents: [ { ...listObjectClientBaseResultItem, - Key: resolvedPath, + Key: resolvePath(inputPath), }, ], NextContinuationToken: nextToken, }; }); const customPageSize = 5; - const response = await list({ - path, + const response = await listPaginatedWrapper({ + path: resolvedPath, options: { pageSize: customPageSize, nextToken: nextToken, }, }); - expect(response.items).toEqual([ - { - ...listResultItem, - path: resolvedPath, - key: resolvedPath, - }, - ]); + const { path, eTag, lastModified, size } = response.items[0]; + expect(response.items).toHaveLength(1); + expect({ path, eTag, lastModified, size }).toEqual({ + ...listResultItem, + path: resolvedPath, + }); expect(response.nextToken).toEqual(nextToken); expect(listObjectsV2).toHaveBeenCalledTimes(1); expect(listObjectsV2).toHaveBeenCalledWith(listObjectClientConfig, { Bucket: bucket, - Prefix: resolvedPath, + Prefix: resolvePath(inputPath), ContinuationToken: nextToken, MaxKeys: customPageSize, }); }, ); - it.each(pathAsFunctionAndStringTests)( + it.each(pathTestCases)( 'should list objects with zero results with custom path: ${path}', async ({ path }) => { - const resolvedPath = resolvePath(path); mockListObject.mockImplementationOnce(() => { return {}; }); - let response = await list({ - path, + let response = await listPaginatedWrapper({ + path: resolvePath(path), }); expect(response.items).toEqual([]); @@ -381,26 +403,28 @@ describe('list API', () => { expect(listObjectsV2).toHaveBeenCalledWith(listObjectClientConfig, { Bucket: bucket, MaxKeys: 1000, - Prefix: resolvedPath, + Prefix: resolvePath(path), }); }, ); - it.each(pathAsFunctionAndStringTests)( + it.each(pathTestCases)( 'should list all objects having three pages with custom path: ${path}', - async ({ path }) => { - const resolvedPath = resolvePath(path); + async ({ path: inputPath }) => { + const resolvedPath = resolvePath(inputPath); mockListObjectsV2ApiWithPages(3); - const result = await list({ - path, + const result = await listAllWrapper({ + path: resolvedPath, options: { listAll: true }, }); const listResult = { - ...listResultItem, path: resolvedPath, - key: resolvedPath, + ...listResultItem, }; + const { path, lastModified, eTag, size } = result.items[0]; + expect(result.items).toHaveLength(3); + expect({ path, lastModified, eTag, size }).toEqual(listResult); expect(result.items).toEqual([listResult, listResult, listResult]); expect(result).not.toHaveProperty(nextToken); diff --git a/packages/storage/__tests__/providers/s3/apis/remove.test.ts b/packages/storage/__tests__/providers/s3/apis/remove.test.ts index 96abf2f976d..0c8662492ac 100644 --- a/packages/storage/__tests__/providers/s3/apis/remove.test.ts +++ b/packages/storage/__tests__/providers/s3/apis/remove.test.ts @@ -2,11 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 import { AWSCredentials } from '@aws-amplify/core/internals/utils'; -import { Amplify } from '@aws-amplify/core'; +import { Amplify, StorageAccessLevel } from '@aws-amplify/core'; import { deleteObject } from '../../../../src/providers/s3/utils/client'; import { remove } from '../../../../src/providers/s3/apis'; -import { StorageOptions } from '../../../../src/types'; import { StorageValidationErrorCode } from '../../../../src/errors/types/validation'; +import { + RemoveInput, + RemoveWithPathInput, + RemoveOutput, + RemoveWithPathOutput, +} from '../../../../src/providers/s3/types'; jest.mock('../../../../src/providers/s3/utils/client'); jest.mock('@aws-amplify/core', () => ({ @@ -23,7 +28,7 @@ jest.mock('@aws-amplify/core', () => ({ const mockDeleteObject = deleteObject as jest.Mock; const mockFetchAuthSession = Amplify.Auth.fetchAuthSession as jest.Mock; const mockGetConfig = Amplify.getConfig as jest.Mock; -const key = 'key'; +const inputKey = 'key'; const bucket = 'bucket'; const region = 'region'; const defaultIdentityId = 'defaultIdentityId'; @@ -55,6 +60,9 @@ describe('remove API', () => { }); describe('Happy Cases', () => { describe('With Key', () => { + const removeWrapper = (input: RemoveInput): Promise => + remove(input); + beforeEach(() => { mockDeleteObject.mockImplementation(() => { return { @@ -65,31 +73,36 @@ describe('remove API', () => { afterEach(() => { jest.clearAllMocks(); }); - [ + const testCases: Array<{ + expectedKey: string; + options?: { accessLevel?: StorageAccessLevel }; + }> = [ { - expectedKey: `public/${key}`, + expectedKey: `public/${inputKey}`, }, { options: { accessLevel: 'guest' }, - expectedKey: `public/${key}`, + expectedKey: `public/${inputKey}`, }, { options: { accessLevel: 'private' }, - expectedKey: `private/${defaultIdentityId}/${key}`, + expectedKey: `private/${defaultIdentityId}/${inputKey}`, }, { options: { accessLevel: 'protected' }, - expectedKey: `protected/${defaultIdentityId}/${key}`, + expectedKey: `protected/${defaultIdentityId}/${inputKey}`, }, - ].forEach(({ options, expectedKey }) => { + ]; + + testCases.forEach(({ options, expectedKey }) => { const accessLevel = options?.accessLevel ?? 'default'; - const removeResultKey = { key }; it(`should remove object with ${accessLevel} accessLevel`, async () => { - expect.assertions(3); - expect( - await remove({ key, options: options as StorageOptions }), - ).toEqual({ ...removeResultKey, path: expectedKey }); + const { key } = await removeWrapper({ + key: inputKey, + options: options, + }); + expect(key).toEqual(inputKey); expect(deleteObject).toHaveBeenCalledTimes(1); expect(deleteObject).toHaveBeenCalledWith(deleteObjectClientConfig, { Bucket: bucket, @@ -99,6 +112,9 @@ describe('remove API', () => { }); }); describe('With Path', () => { + const removeWrapper = ( + input: RemoveWithPathInput, + ): Promise => remove(input); beforeEach(() => { mockDeleteObject.mockImplementation(() => { return { @@ -111,35 +127,32 @@ describe('remove API', () => { }); [ { - path: `public/${key}`, + path: `public/${inputKey}`, }, { - path: ({ identityId }: any) => `protected/${identityId}/${key}`, + path: ({ identityId }: { identityId?: string }) => + `protected/${identityId}/${inputKey}`, }, - ].forEach(({ path }) => { - const resolvePath = - typeof path === 'string' - ? path - : path({ identityId: defaultIdentityId }); - const removeResultPath = { - path: resolvePath, - key: resolvePath, - }; + ].forEach(({ path: inputPath }) => { + const resolvedPath = + typeof inputPath === 'string' + ? inputPath + : inputPath({ identityId: defaultIdentityId }); it(`should remove object for the given path`, async () => { - expect.assertions(3); - expect(await remove({ path })).toEqual(removeResultPath); + const { path } = await removeWrapper({ path: inputPath }); + expect(path).toEqual(resolvedPath); expect(deleteObject).toHaveBeenCalledTimes(1); expect(deleteObject).toHaveBeenCalledWith(deleteObjectClientConfig, { Bucket: bucket, - Key: resolvePath, + Key: resolvedPath, }); }); }); }); }); - describe('Error Cases', () => { + describe('Error Cases:', () => { afterEach(() => { jest.clearAllMocks(); }); diff --git a/packages/storage/__tests__/providers/s3/apis/uploadData/index.test.ts b/packages/storage/__tests__/providers/s3/apis/uploadData/index.test.ts index df818c254c8..211d3238a35 100644 --- a/packages/storage/__tests__/providers/s3/apis/uploadData/index.test.ts +++ b/packages/storage/__tests__/providers/s3/apis/uploadData/index.test.ts @@ -10,6 +10,7 @@ import { } from '../../../../../src/errors/types/validation'; import { putObjectJob } from '../../../../../src/providers/s3/apis/uploadData/putObjectJob'; import { getMultipartUploadHandlers } from '../../../../../src/providers/s3/apis/uploadData/multipart'; +import { UploadDataInput, UploadDataWithPathInput } from '../../../../../src'; jest.mock('../../../../../src/providers/s3/utils/'); jest.mock('../../../../../src/providers/s3/apis/uploadData/putObjectJob'); @@ -35,12 +36,11 @@ describe('uploadData with key', () => { describe('validation', () => { it('should throw if data size is too big', async () => { - expect(() => - uploadData({ - key: 'key', - data: { size: MAX_OBJECT_SIZE + 1 } as any, - }), - ).toThrow( + const mockUploadInput: UploadDataInput = { + key: 'key', + data: { size: MAX_OBJECT_SIZE + 1 } as any, + }; + expect(() => uploadData(mockUploadInput)).toThrow( expect.objectContaining( validationErrorMap[StorageValidationErrorCode.ObjectIsTooLarge], ), @@ -131,12 +131,11 @@ describe('uploadData with path', () => { describe('validation', () => { it('should throw if data size is too big', async () => { - expect(() => - uploadData({ - path: testPath, - data: { size: MAX_OBJECT_SIZE + 1 } as any, - }), - ).toThrow( + const mockUploadInput: UploadDataWithPathInput = { + path: testPath, + data: { size: MAX_OBJECT_SIZE + 1 } as any, + }; + expect(() => uploadData(mockUploadInput)).toThrow( expect.objectContaining( validationErrorMap[StorageValidationErrorCode.ObjectIsTooLarge], ), @@ -154,7 +153,7 @@ describe('uploadData with path', () => { describe('use putObject for small uploads', () => { const smallData = { size: 5 * 1024 * 1024 } as any; - + test.each([ { path: testPath, @@ -163,22 +162,22 @@ describe('uploadData with path', () => { path: () => testPath, }, ])( - 'should use putObject if data size is <= 5MB when path is $path', + 'should use putObject if data size is <= 5MB when path is $path', async ({ path }) => { const testInput = { path, data: smallData, - } + }; uploadData(testInput); expect(mockPutObjectJob).toHaveBeenCalledWith( - testInput, - expect.any(AbortSignal), - expect.any(Number) + testInput, + expect.any(AbortSignal), + expect.any(Number), ); expect(mockGetMultipartUploadHandlers).not.toHaveBeenCalled(); - } + }, ); it('should use uploadTask', async () => { @@ -207,12 +206,15 @@ describe('uploadData with path', () => { const testInput = { path: testPath, data: biggerData, - } + }; uploadData(testInput); expect(mockPutObjectJob).not.toHaveBeenCalled(); - expect(mockGetMultipartUploadHandlers).toHaveBeenCalledWith(testInput, expect.any(Number)); + expect(mockGetMultipartUploadHandlers).toHaveBeenCalledWith( + testInput, + expect.any(Number), + ); }); it('should use uploadTask', async () => { diff --git a/packages/storage/__tests__/providers/s3/apis/uploadData/multipartHandlers.test.ts b/packages/storage/__tests__/providers/s3/apis/uploadData/multipartHandlers.test.ts index 96e680088bb..302d76beaa8 100644 --- a/packages/storage/__tests__/providers/s3/apis/uploadData/multipartHandlers.test.ts +++ b/packages/storage/__tests__/providers/s3/apis/uploadData/multipartHandlers.test.ts @@ -678,10 +678,11 @@ describe('getMultipartUploadHandlers with path', () => { expectedKey: testPath, }, { - path: ({identityId}: any) => `testPath/${identityId}/object`, + path: ({ identityId }: { identityId?: string }) => + `testPath/${identityId}/object`, expectedKey: `testPath/${defaultIdentityId}/object`, }, - ].forEach(({ path, expectedKey }) => { + ].forEach(({ path: inputPath, expectedKey }) => { it.each([ ['file', new File([getBlob(8 * MB)], 'someName')], ['blob', getBlob(8 * MB)], @@ -693,7 +694,7 @@ describe('getMultipartUploadHandlers with path', () => { async (_, twoPartsPayload) => { mockMultipartUploadSuccess(); const { multipartUploadJob } = getMultipartUploadHandlers({ - path: path, + path: inputPath, data: twoPartsPayload, }); const result = await multipartUploadJob(); @@ -910,7 +911,9 @@ describe('getMultipartUploadHandlers with path', () => { const lastModifiedRegex = /someName_\d{13}_/; expect(Object.keys(cacheValue)).toEqual([ - expect.stringMatching(new RegExp(lastModifiedRegex.source + testPathCacheKey)), + expect.stringMatching( + new RegExp(lastModifiedRegex.source + testPathCacheKey), + ), ]); }); diff --git a/packages/storage/__tests__/providers/s3/apis/uploadData/putObjectJob.test.ts b/packages/storage/__tests__/providers/s3/apis/uploadData/putObjectJob.test.ts index 6106ff8fa46..b03822946da 100644 --- a/packages/storage/__tests__/providers/s3/apis/uploadData/putObjectJob.test.ts +++ b/packages/storage/__tests__/providers/s3/apis/uploadData/putObjectJob.test.ts @@ -57,25 +57,24 @@ mockPutObject.mockResolvedValue({ describe('putObjectJob with key', () => { it('should supply the correct parameters to putObject API handler', async () => { const abortController = new AbortController(); - const key = 'key'; - const finalKey = `public/${key}`; + const inputKey = 'key'; const data = 'data'; - const contentType = 'contentType'; + const mockContentType = 'contentType'; const contentDisposition = 'contentDisposition'; const contentEncoding = 'contentEncoding'; - const metadata = { key: 'value' }; + const mockMetadata = { key: 'value' }; const onProgress = jest.fn(); const useAccelerateEndpoint = true; const job = putObjectJob( { - key, + key: inputKey, data, options: { contentDisposition, contentEncoding, - contentType, - metadata, + contentType: mockContentType, + metadata: mockMetadata, onProgress, useAccelerateEndpoint, }, @@ -84,8 +83,7 @@ describe('putObjectJob with key', () => { ); const result = await job(); expect(result).toEqual({ - key, - path: finalKey, + key: inputKey, eTag: 'eTag', versionId: 'versionId', contentType: 'contentType', @@ -103,12 +101,12 @@ describe('putObjectJob with key', () => { }, { Bucket: 'bucket', - Key: finalKey, + Key: `public/${inputKey}`, Body: data, - ContentType: contentType, + ContentType: mockContentType, ContentDisposition: contentDisposition, ContentEncoding: contentEncoding, - Metadata: metadata, + Metadata: mockMetadata, ContentMD5: undefined, }, ); @@ -146,25 +144,25 @@ describe('putObjectJob with path', () => { }, ])( 'should supply the correct parameters to putObject API handler when path is $path', - async ({ path, expectedKey }) => { + async ({ path: inputPath, expectedKey }) => { const abortController = new AbortController(); const data = 'data'; - const contentType = 'contentType'; + const mockContentType = 'contentType'; const contentDisposition = 'contentDisposition'; const contentEncoding = 'contentEncoding'; - const metadata = { key: 'value' }; + const mockMetadata = { key: 'value' }; const onProgress = jest.fn(); const useAccelerateEndpoint = true; const job = putObjectJob( { - path, + path: inputPath, data, options: { contentDisposition, contentEncoding, - contentType, - metadata, + contentType: mockContentType, + metadata: mockMetadata, onProgress, useAccelerateEndpoint, }, @@ -174,7 +172,6 @@ describe('putObjectJob with path', () => { const result = await job(); expect(result).toEqual({ path: expectedKey, - key: expectedKey, eTag: 'eTag', versionId: 'versionId', contentType: 'contentType', @@ -194,10 +191,10 @@ describe('putObjectJob with path', () => { Bucket: 'bucket', Key: expectedKey, Body: data, - ContentType: contentType, + ContentType: mockContentType, ContentDisposition: contentDisposition, ContentEncoding: contentEncoding, - Metadata: metadata, + Metadata: mockMetadata, ContentMD5: undefined, }, ); diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts index 6183ed3a2d5..45bf9734a66 100644 --- a/packages/storage/src/index.ts +++ b/packages/storage/src/index.ts @@ -13,24 +13,40 @@ export { export { UploadDataInput, + UploadDataWithPathInput, DownloadDataInput, + DownloadDataWithPathInput, RemoveInput, + RemoveWithPathInput, ListAllInput, + ListAllWithPathInput, ListPaginateInput, + ListPaginateWithPathInput, GetPropertiesInput, + GetPropertiesWithPathInput, CopyInput, + CopyWithPathInput, GetUrlInput, + GetUrlWithPathInput, } from './providers/s3/types/inputs'; export { UploadDataOutput, + UploadDataWithPathOutput, DownloadDataOutput, + DownloadDataWithPathOutput, RemoveOutput, + RemoveWithPathOutput, ListAllOutput, + ListAllWithPathOutput, ListPaginateOutput, + ListPaginateWithPathOutput, GetPropertiesOutput, + GetPropertiesWithPathOutput, CopyOutput, + CopyWithPathOutput, GetUrlOutput, + GetUrlWithPathOutput, } from './providers/s3/types/outputs'; export { TransferProgressEvent } from './types'; diff --git a/packages/storage/src/providers/s3/apis/copy.ts b/packages/storage/src/providers/s3/apis/copy.ts index 19cfbf034db..763ff45829b 100644 --- a/packages/storage/src/providers/s3/apis/copy.ts +++ b/packages/storage/src/providers/s3/apis/copy.ts @@ -5,41 +5,38 @@ import { Amplify } from '@aws-amplify/core'; import { CopyInput, - CopyInputWithKey, - CopyInputWithPath, CopyOutput, + CopyWithPathInput, + CopyWithPathOutput, } from '../types'; import { copy as copyInternal } from './internal/copy'; -interface Copy { - /** - * Copy an object from a source to a destination object within the same bucket. - * - * @param input - The CopyInputWithPath object. - * @returns Output containing the destination object path. - * @throws service: `S3Exception` - Thrown when checking for existence of the object - * @throws validation: `StorageValidationErrorCode` - Thrown when - * source or destination path is not defined. - */ - (input: CopyInputWithPath): Promise; - /** - * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/copy | path} instead. - * - * Copy an object from a source to a destination object within the same bucket. Can optionally copy files across - * different accessLevel or identityId (if source object's accessLevel is 'protected'). - * - * @param input - The CopyInputWithKey object. - * @returns Output containing the destination object key. - * @throws service: `S3Exception` - Thrown when checking for existence of the object - * @throws validation: `StorageValidationErrorCode` - Thrown when - * source or destination key is not defined. - */ - (input: CopyInputWithKey): Promise; - (input: CopyInput): Promise; -} +/** + * Copy an object from a source to a destination object within the same bucket. + * + * @param input - The `CopyWithPathInput` object. + * @returns Output containing the destination object path. + * @throws service: `S3Exception` - Thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Thrown when + * source or destination path is not defined. + */ +export function copy(input: CopyWithPathInput): Promise; +/** + * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/copy | path} instead. + * + * Copy an object from a source to a destination object within the same bucket. Can optionally copy files across + * different accessLevel or identityId (if source object's accessLevel is 'protected'). + * + * @param input - The `CopyInput` object. + * @returns Output containing the destination object key. + * @throws service: `S3Exception` - Thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Thrown when + * source or destination key is not defined. + */ +export function copy(input: CopyInput): Promise; -export const copy: Copy = ( - input: CopyInput, -): Promise => copyInternal(Amplify, input) as Promise; +export function copy(input: CopyInput | CopyWithPathInput) { + return copyInternal(Amplify, input); +} diff --git a/packages/storage/src/providers/s3/apis/downloadData.ts b/packages/storage/src/providers/s3/apis/downloadData.ts index ff12d6b55c3..7c98ee2b857 100644 --- a/packages/storage/src/providers/s3/apis/downloadData.ts +++ b/packages/storage/src/providers/s3/apis/downloadData.ts @@ -6,91 +6,93 @@ import { StorageAction } from '@aws-amplify/core/internals/utils'; import { DownloadDataInput, - DownloadDataInputWithKey, - DownloadDataInputWithPath, DownloadDataOutput, - ItemWithKeyAndPath, + DownloadDataWithPathInput, + DownloadDataWithPathOutput, } from '../types'; import { resolveS3ConfigAndInput } from '../utils/resolveS3ConfigAndInput'; import { createDownloadTask, validateStorageOperationInput } from '../utils'; import { getObject } from '../utils/client'; import { getStorageUserAgentValue } from '../utils/userAgent'; import { logger } from '../../../utils'; -import { StorageDownloadDataOutput } from '../../../types'; +import { + StorageDownloadDataOutput, + StorageItemWithKey, + StorageItemWithPath, +} from '../../../types'; import { STORAGE_INPUT_KEY } from '../utils/constants'; -interface DownloadData { - /** - * Download S3 object data to memory - * - * @param input - The DownloadDataInputWithPath object. - * @returns A cancelable task exposing result promise from `result` property. - * @throws service: `S3Exception` - thrown when checking for existence of the object - * @throws validation: `StorageValidationErrorCode` - Validation errors - * - * @example - * ```ts - * // Download a file from s3 bucket - * const { body, eTag } = await downloadData({ path, options: { - * onProgress, // Optional progress callback. - * } }).result; - * ``` - * @example - * ```ts - * // Cancel a task - * const downloadTask = downloadData({ path }); - * //... - * downloadTask.cancel(); - * try { - * await downloadTask.result; - * } catch (error) { - * if(isCancelError(error)) { - * // Handle error thrown by task cancelation. - * } - * } - *``` - */ - (input: DownloadDataInputWithPath): DownloadDataOutput; - /** - * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/download/#downloaddata | path} instead. - * - * Download S3 object data to memory - * - * @param input - The DownloadDataInputWithKey object. - * @returns A cancelable task exposing result promise from `result` property. - * @throws service: `S3Exception` - thrown when checking for existence of the object - * @throws validation: `StorageValidationErrorCode` - Validation errors - * - * @example - * ```ts - * // Download a file from s3 bucket - * const { body, eTag } = await downloadData({ key, options: { - * onProgress, // Optional progress callback. - * } }).result; - * ``` - * @example - * ```ts - * // Cancel a task - * const downloadTask = downloadData({ key }); - * //... - * downloadTask.cancel(); - * try { - * await downloadTask.result; - * } catch (error) { - * if(isCancelError(error)) { - * // Handle error thrown by task cancelation. - * } - * } - *``` - */ - (input: DownloadDataInputWithKey): DownloadDataOutput; - (input: DownloadDataInput): DownloadDataOutput; -} +/** + * Download S3 object data to memory + * + * @param input - The `DownloadDataWithPathInput` object. + * @returns A cancelable task exposing result promise from `result` property. + * @throws service: `S3Exception` - thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Validation errors + * + * @example + * ```ts + * // Download a file from s3 bucket + * const { body, eTag } = await downloadData({ path, options: { + * onProgress, // Optional progress callback. + * } }).result; + * ``` + * @example + * ```ts + * // Cancel a task + * const downloadTask = downloadData({ path }); + * //... + * downloadTask.cancel(); + * try { + * await downloadTask.result; + * } catch (error) { + * if(isCancelError(error)) { + * // Handle error thrown by task cancelation. + * } + * } + *``` + */ +export function downloadData( + input: DownloadDataWithPathInput, +): DownloadDataWithPathOutput; +/** + * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/download/#downloaddata | path} instead. + * + * Download S3 object data to memory + * + * @param input - The `DownloadDataInput` object. + * @returns A cancelable task exposing result promise from `result` property. + * @throws service: `S3Exception` - thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Validation errors + * + * @example + * ```ts + * // Download a file from s3 bucket + * const { body, eTag } = await downloadData({ key, options: { + * onProgress, // Optional progress callback. + * } }).result; + * ``` + * @example + * ```ts + * // Cancel a task + * const downloadTask = downloadData({ key }); + * //... + * downloadTask.cancel(); + * try { + * await downloadTask.result; + * } catch (error) { + * if(isCancelError(error)) { + * // Handle error thrown by task cancelation. + * } + * } + *``` + */ +export function downloadData(input: DownloadDataInput): DownloadDataOutput; -export const downloadData: DownloadData = ( - input: DownloadDataInput, -): Output => { +export function downloadData( + input: DownloadDataInput | DownloadDataWithPathInput, +) { const abortController = new AbortController(); const downloadTask = createDownloadTask({ @@ -100,12 +102,17 @@ export const downloadData: DownloadData = ( }, }); - return downloadTask as Output; -}; + return downloadTask; +} const downloadDataJob = - (downloadDataInput: DownloadDataInput, abortSignal: AbortSignal) => - async (): Promise> => { + ( + downloadDataInput: DownloadDataInput | DownloadDataWithPathInput, + abortSignal: AbortSignal, + ) => + async (): Promise< + StorageDownloadDataOutput + > => { const { options: downloadDataOptions } = downloadDataInput; const { bucket, keyPrefix, s3Config, identityId } = await resolveS3ConfigAndInput(Amplify, downloadDataOptions); @@ -153,6 +160,6 @@ const downloadDataJob = }; return inputType === STORAGE_INPUT_KEY - ? { key: objectKey, path: finalKey, ...result } - : { path: finalKey, key: finalKey, ...result }; + ? { key: objectKey, ...result } + : { path: objectKey, ...result }; }; diff --git a/packages/storage/src/providers/s3/apis/getProperties.ts b/packages/storage/src/providers/s3/apis/getProperties.ts index c5e866922b4..630d0b1c467 100644 --- a/packages/storage/src/providers/s3/apis/getProperties.ts +++ b/packages/storage/src/providers/s3/apis/getProperties.ts @@ -5,42 +5,43 @@ import { Amplify } from '@aws-amplify/core'; import { GetPropertiesInput, - GetPropertiesInputWithKey, - GetPropertiesInputWithPath, GetPropertiesOutput, + GetPropertiesWithPathInput, + GetPropertiesWithPathOutput, } from '../types'; import { getProperties as getPropertiesInternal } from './internal/getProperties'; -interface GetProperties { - /** - * Gets the properties of a file. The properties include S3 system metadata and - * the user metadata that was provided when uploading the file. - * - * @param input - The `GetPropertiesInputWithPath` object. - * @returns Requested object properties. - * @throws An `S3Exception` when the underlying S3 service returned error. - * @throws A `StorageValidationErrorCode` when API call parameters are invalid. - */ - (input: GetPropertiesInputWithPath): Promise; - /** - * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/javascript/build-a-backend/storage/get-properties/ | path} instead. - * - * Gets the properties of a file. The properties include S3 system metadata and - * the user metadata that was provided when uploading the file. - * - * @param input - The `GetPropertiesInputWithKey` object. - * @returns Requested object properties. - * @throws An `S3Exception` when the underlying S3 service returned error. - * @throws A `StorageValidationErrorCode` when API call parameters are invalid. - */ - (input: GetPropertiesInputWithKey): Promise; - (input: GetPropertiesInput): Promise; -} - -export const getProperties: GetProperties = < - Output extends GetPropertiesOutput, ->( +/** + * Gets the properties of a file. The properties include S3 system metadata and + * the user metadata that was provided when uploading the file. + * + * @param input - The `GetPropertiesWithPathInput` object. + * @returns Requested object properties. + * @throws An `S3Exception` when the underlying S3 service returned error. + * @throws A `StorageValidationErrorCode` when API call parameters are invalid. + */ +export function getProperties( + input: GetPropertiesWithPathInput, +): Promise; +/** + * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/javascript/build-a-backend/storage/get-properties/ | path} instead. + * + * Gets the properties of a file. The properties include S3 system metadata and + * the user metadata that was provided when uploading the file. + * + * @param input - The `GetPropertiesInput` object. + * @returns Requested object properties. + * @throws An `S3Exception` when the underlying S3 service returned error. + * @throws A `StorageValidationErrorCode` when API call parameters are invalid. + */ +export function getProperties( input: GetPropertiesInput, -): Promise => getPropertiesInternal(Amplify, input) as Promise; +): Promise; + +export function getProperties( + input: GetPropertiesInput | GetPropertiesWithPathInput, +) { + return getPropertiesInternal(Amplify, input); +} diff --git a/packages/storage/src/providers/s3/apis/getUrl.ts b/packages/storage/src/providers/s3/apis/getUrl.ts index 24874da81d2..aafe1f282b3 100644 --- a/packages/storage/src/providers/s3/apis/getUrl.ts +++ b/packages/storage/src/providers/s3/apis/getUrl.ts @@ -5,53 +5,53 @@ import { Amplify } from '@aws-amplify/core'; import { GetUrlInput, - GetUrlInputWithKey, - GetUrlInputWithPath, GetUrlOutput, + GetUrlWithPathInput, + GetUrlWithPathOutput, } from '../types'; import { getUrl as getUrlInternal } from './internal/getUrl'; -interface GetUrl { - /** - * Get a temporary presigned URL to download the specified S3 object. - * The presigned URL expires when the associated role used to sign the request expires or - * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. - * - * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` - * to true, this method will verify the given object already exists in S3 before returning a presigned - * URL, and will throw `StorageError` if the object does not exist. - * - * @param input - The `GetUrlInputWithPath` object. - * @returns Presigned URL and timestamp when the URL MAY expire. - * @throws service: `S3Exception` - thrown when checking for existence of the object - * @throws validation: `StorageValidationErrorCode` - Validation errors - * thrown either username or key are not defined. - * - */ - (input: GetUrlInputWithPath): Promise; - /** - * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/javascript/build-a-backend/storage/download/#generate-a-download-url | path} instead. - * - * Get a temporary presigned URL to download the specified S3 object. - * The presigned URL expires when the associated role used to sign the request expires or - * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. - * - * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` - * to true, this method will verify the given object already exists in S3 before returning a presigned - * URL, and will throw `StorageError` if the object does not exist. - * - * @param input - The `GetUrlInputWithKey` object. - * @returns Presigned URL and timestamp when the URL MAY expire. - * @throws service: `S3Exception` - thrown when checking for existence of the object - * @throws validation: `StorageValidationErrorCode` - Validation errors - * thrown either username or key are not defined. - * - */ - (input: GetUrlInputWithKey): Promise; - (input: GetUrlInput): Promise; -} +/** + * Get a temporary presigned URL to download the specified S3 object. + * The presigned URL expires when the associated role used to sign the request expires or + * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. + * + * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` + * to true, this method will verify the given object already exists in S3 before returning a presigned + * URL, and will throw `StorageError` if the object does not exist. + * + * @param input - The `GetUrlWithPathInput` object. + * @returns Presigned URL and timestamp when the URL may expire. + * @throws service: `S3Exception` - thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Validation errors + * thrown either username or key are not defined. + * + */ +export function getUrl( + input: GetUrlWithPathInput, +): Promise; +/** + * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/javascript/build-a-backend/storage/download/#generate-a-download-url | path} instead. + * + * Get a temporary presigned URL to download the specified S3 object. + * The presigned URL expires when the associated role used to sign the request expires or + * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. + * + * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` + * to true, this method will verify the given object already exists in S3 before returning a presigned + * URL, and will throw `StorageError` if the object does not exist. + * + * @param input - The `GetUrlInput` object. + * @returns Presigned URL and timestamp when the URL may expire. + * @throws service: `S3Exception` - thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Validation errors + * thrown either username or key are not defined. + * + */ +export function getUrl(input: GetUrlInput): Promise; -export const getUrl: GetUrl = (input: GetUrlInput): Promise => - getUrlInternal(Amplify, input); +export function getUrl(input: GetUrlInput | GetUrlWithPathInput) { + return getUrlInternal(Amplify, input); +} diff --git a/packages/storage/src/providers/s3/apis/internal/copy.ts b/packages/storage/src/providers/s3/apis/internal/copy.ts index 7882a3429dc..e0c96a1fba4 100644 --- a/packages/storage/src/providers/s3/apis/internal/copy.ts +++ b/packages/storage/src/providers/s3/apis/internal/copy.ts @@ -6,9 +6,9 @@ import { StorageAction } from '@aws-amplify/core/internals/utils'; import { CopyInput, - CopyInputWithKey, - CopyInputWithPath, CopyOutput, + CopyWithPathInput, + CopyWithPathOutput, } from '../../types'; import { ResolvedS3Config } from '../../types/options'; import { @@ -22,13 +22,14 @@ import { copyObject } from '../../utils/client'; import { getStorageUserAgentValue } from '../../utils/userAgent'; import { logger } from '../../../../utils'; -const isCopyInputWithPath = (input: CopyInput): input is CopyInputWithPath => - isInputWithPath(input.source); +const isCopyInputWithPath = ( + input: CopyInput | CopyWithPathInput, +): input is CopyWithPathInput => isInputWithPath(input.source); export const copy = async ( amplify: AmplifyClassV6, - input: CopyInput, -): Promise => { + input: CopyInput | CopyWithPathInput, +): Promise => { return isCopyInputWithPath(input) ? copyWithPath(amplify, input) : copyWithKey(amplify, input); @@ -36,8 +37,8 @@ export const copy = async ( const copyWithPath = async ( amplify: AmplifyClassV6, - input: CopyInputWithPath, -): Promise => { + input: CopyWithPathInput, +): Promise => { const { source, destination } = input; const { s3Config, bucket, identityId } = await resolveS3ConfigAndInput(amplify); @@ -68,13 +69,13 @@ const copyWithPath = async ( s3Config, }); - return { path: finalCopyDestination, key: finalCopyDestination }; + return { path: finalCopyDestination }; }; /** @deprecated Use {@link copyWithPath} instead. */ export const copyWithKey = async ( amplify: AmplifyClassV6, - input: CopyInputWithKey, + input: CopyInput, ): Promise => { const { source: { key: sourceKey }, @@ -111,7 +112,6 @@ export const copyWithKey = async ( return { key: destinationKey, - path: finalCopyDestination, }; }; diff --git a/packages/storage/src/providers/s3/apis/internal/getProperties.ts b/packages/storage/src/providers/s3/apis/internal/getProperties.ts index e2dd8a19780..3b61460d89b 100644 --- a/packages/storage/src/providers/s3/apis/internal/getProperties.ts +++ b/packages/storage/src/providers/s3/apis/internal/getProperties.ts @@ -4,7 +4,12 @@ import { AmplifyClassV6 } from '@aws-amplify/core'; import { StorageAction } from '@aws-amplify/core/internals/utils'; -import { GetPropertiesInput, GetPropertiesOutput } from '../../types'; +import { + GetPropertiesInput, + GetPropertiesOutput, + GetPropertiesWithPathInput, + GetPropertiesWithPathOutput, +} from '../../types'; import { resolveS3ConfigAndInput, validateStorageOperationInput, @@ -16,9 +21,9 @@ import { STORAGE_INPUT_KEY } from '../../utils/constants'; export const getProperties = async ( amplify: AmplifyClassV6, - input: GetPropertiesInput, + input: GetPropertiesInput | GetPropertiesWithPathInput, action?: StorageAction, -): Promise => { +): Promise => { const { options: getPropertiesOptions } = input; const { s3Config, bucket, keyPrefix, identityId } = await resolveS3ConfigAndInput(amplify, getPropertiesOptions); @@ -53,6 +58,6 @@ export const getProperties = async ( }; return inputType === STORAGE_INPUT_KEY - ? { key: objectKey, path: finalKey, ...result } - : { path: finalKey, key: finalKey, ...result }; + ? { key: objectKey, ...result } + : { path: objectKey, ...result }; }; diff --git a/packages/storage/src/providers/s3/apis/internal/getUrl.ts b/packages/storage/src/providers/s3/apis/internal/getUrl.ts index ea2fd60d741..a2de5d3f770 100644 --- a/packages/storage/src/providers/s3/apis/internal/getUrl.ts +++ b/packages/storage/src/providers/s3/apis/internal/getUrl.ts @@ -4,7 +4,12 @@ import { AmplifyClassV6 } from '@aws-amplify/core'; import { StorageAction } from '@aws-amplify/core/internals/utils'; -import { GetUrlInput, GetUrlOutput } from '../../types'; +import { + GetUrlInput, + GetUrlOutput, + GetUrlWithPathInput, + GetUrlWithPathOutput, +} from '../../types'; import { StorageValidationErrorCode } from '../../../../errors/types/validation'; import { getPresignedGetObjectUrl } from '../../utils/client'; import { @@ -22,8 +27,8 @@ import { getProperties } from './getProperties'; export const getUrl = async ( amplify: AmplifyClassV6, - input: GetUrlInput, -): Promise => { + input: GetUrlInput | GetUrlWithPathInput, +): Promise => { const { options: getUrlOptions } = input; const { s3Config, keyPrefix, bucket, identityId } = await resolveS3ConfigAndInput(amplify, getUrlOptions); @@ -36,16 +41,7 @@ export const getUrl = async ( inputType === STORAGE_INPUT_KEY ? keyPrefix + objectKey : objectKey; if (getUrlOptions?.validateObjectExistence) { - await getProperties( - amplify, - { - options: getUrlOptions, - ...((inputType === STORAGE_INPUT_KEY - ? { key: input.key } - : { path: input.path }) as GetUrlInput), - }, - StorageAction.GetUrl, - ); + await getProperties(amplify, input, StorageAction.GetUrl); } let urlExpirationInSec = diff --git a/packages/storage/src/providers/s3/apis/internal/list.ts b/packages/storage/src/providers/s3/apis/internal/list.ts index bf17316d495..f180dfe5247 100644 --- a/packages/storage/src/providers/s3/apis/internal/list.ts +++ b/packages/storage/src/providers/s3/apis/internal/list.ts @@ -7,8 +7,14 @@ import { StorageAction } from '@aws-amplify/core/internals/utils'; import { ListAllInput, ListAllOutput, + ListAllWithPathInput, + ListAllWithPathOutput, + ListOutputItem, + ListOutputItemWithPath, ListPaginateInput, ListPaginateOutput, + ListPaginateWithPathInput, + ListPaginateWithPathOutput, } from '../../types'; import { resolveS3ConfigAndInput, @@ -23,7 +29,6 @@ import { import { getStorageUserAgentValue } from '../../utils/userAgent'; import { logger } from '../../../../utils'; import { STORAGE_INPUT_PREFIX } from '../../utils/constants'; -import { ListOutputItem } from '../../types/outputs'; const MAX_PAGE_SIZE = 1000; @@ -35,8 +40,17 @@ interface ListInputArgs { export const list = async ( amplify: AmplifyClassV6, - input: ListAllInput | ListPaginateInput, -): Promise => { + input: + | ListAllInput + | ListPaginateInput + | ListAllWithPathInput + | ListPaginateWithPathInput, +): Promise< + | ListAllOutput + | ListPaginateOutput + | ListAllWithPathOutput + | ListPaginateWithPathOutput +> => { const { options = {} } = input; const { s3Config, @@ -145,19 +159,14 @@ const _listWithPrefix = async ({ } return { - items: response.Contents.map(item => { - const finalKey = generatedPrefix + items: response.Contents.map(item => ({ + key: generatedPrefix ? item.Key!.substring(generatedPrefix.length) - : item.Key!; - - return { - key: finalKey, - path: item.Key!, - eTag: item.ETag, - lastModified: item.LastModified, - size: item.Size, - }; - }), + : item.Key!, + eTag: item.ETag, + lastModified: item.LastModified, + size: item.Size, + })), nextToken: response.NextContinuationToken, }; }; @@ -165,8 +174,8 @@ const _listWithPrefix = async ({ const _listAllWithPath = async ({ s3Config, listParams, -}: ListInputArgs): Promise => { - const listResult: ListOutputItem[] = []; +}: ListInputArgs): Promise => { + const listResult: ListOutputItemWithPath[] = []; let continuationToken = listParams.ContinuationToken; do { const { items: pageResults, nextToken: pageNextToken } = @@ -190,7 +199,7 @@ const _listAllWithPath = async ({ const _listWithPath = async ({ s3Config, listParams, -}: ListInputArgs): Promise => { +}: ListInputArgs): Promise => { const listParamsClone = { ...listParams }; if (!listParamsClone.MaxKeys || listParamsClone.MaxKeys > MAX_PAGE_SIZE) { logger.debug(`defaulting pageSize to ${MAX_PAGE_SIZE}.`); @@ -214,7 +223,6 @@ const _listWithPath = async ({ return { items: response.Contents.map(item => ({ path: item.Key!, - key: item.Key!, eTag: item.ETag, lastModified: item.LastModified, size: item.Size, diff --git a/packages/storage/src/providers/s3/apis/internal/remove.ts b/packages/storage/src/providers/s3/apis/internal/remove.ts index 492b35c2362..bc0fa4a2ade 100644 --- a/packages/storage/src/providers/s3/apis/internal/remove.ts +++ b/packages/storage/src/providers/s3/apis/internal/remove.ts @@ -4,7 +4,12 @@ import { AmplifyClassV6 } from '@aws-amplify/core'; import { StorageAction } from '@aws-amplify/core/internals/utils'; -import { RemoveInput, RemoveOutput } from '../../types'; +import { + RemoveInput, + RemoveOutput, + RemoveWithPathInput, + RemoveWithPathOutput, +} from '../../types'; import { resolveS3ConfigAndInput, validateStorageOperationInput, @@ -16,8 +21,8 @@ import { STORAGE_INPUT_KEY } from '../../utils/constants'; export const remove = async ( amplify: AmplifyClassV6, - input: RemoveInput, -): Promise => { + input: RemoveInput | RemoveWithPathInput, +): Promise => { const { options = {} } = input ?? {}; const { s3Config, keyPrefix, bucket, identityId } = await resolveS3ConfigAndInput(amplify, options); @@ -50,10 +55,8 @@ export const remove = async ( return inputType === STORAGE_INPUT_KEY ? { key: objectKey, - path: finalKey, } : { - path: finalKey, - key: finalKey, + path: objectKey, }; }; diff --git a/packages/storage/src/providers/s3/apis/list.ts b/packages/storage/src/providers/s3/apis/list.ts index 2383ebfcca1..cd58dbdaacd 100644 --- a/packages/storage/src/providers/s3/apis/list.ts +++ b/packages/storage/src/providers/s3/apis/list.ts @@ -4,62 +4,66 @@ import { Amplify } from '@aws-amplify/core'; import { ListAllInput, - ListAllInputWithPath, - ListAllInputWithPrefix, ListAllOutput, + ListAllWithPathInput, + ListAllWithPathOutput, ListPaginateInput, - ListPaginateInputWithPath, - ListPaginateInputWithPrefix, ListPaginateOutput, + ListPaginateWithPathInput, + ListPaginateWithPathOutput, } from '../types'; import { list as listInternal } from './internal/list'; -interface ListApi { - /** - * List files in pages with the given `path`. - * `pageSize` is defaulted to 1000. Additionally, the result will include a `nextToken` if there are more items to retrieve. - * @param input - The `ListPaginateInputWithPath` object. - * @returns A list of objects with path and metadata - * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket - * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials - */ - (input: ListPaginateInputWithPath): Promise; - /** - * List all files from S3 for a given `path`. You can set `listAll` to true in `options` to get all the files from S3. - * @param input - The `ListAllInputWithPath` object. - * @returns A list of all objects with path and metadata - * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket - * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials - */ - (input: ListAllInputWithPath): Promise; - /** - * @deprecated The `prefix` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/list | path} instead. - * List files in pages with the given `prefix`. - * `pageSize` is defaulted to 1000. Additionally, the result will include a `nextToken` if there are more items to retrieve. - * @param input - The `ListPaginateInputWithPrefix` object. - * @returns A list of objects with key and metadata - * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket - * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials - */ - (input?: ListPaginateInputWithPrefix): Promise; - /** - * @deprecated The `prefix` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/list | path} instead. - * List all files from S3 for a given `prefix`. You can set `listAll` to true in `options` to get all the files from S3. - * @param input - The `ListAllInputWithPrefix` object. - * @returns A list of all objects with key and metadata - * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket - * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials - */ - (input?: ListAllInputWithPrefix): Promise; - (input?: ListAllInput): Promise; - (input?: ListPaginateInput): Promise; -} +/** + * List files in pages with the given `path`. + * `pageSize` is defaulted to 1000. Additionally, the result will include a `nextToken` if there are more items to retrieve. + * @param input - The `ListPaginateWithPathInput` object. + * @returns A list of objects with path and metadata + * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket + * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials + */ +export function list( + input: ListPaginateWithPathInput, +): Promise; +/** + * List all files from S3 for a given `path`. You can set `listAll` to true in `options` to get all the files from S3. + * @param input - The `ListAllWithPathInput` object. + * @returns A list of all objects with path and metadata + * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket + * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials + */ +export function list( + input: ListAllWithPathInput, +): Promise; +/** + * @deprecated The `prefix` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/list | path} instead. + * List files in pages with the given `prefix`. + * `pageSize` is defaulted to 1000. Additionally, the result will include a `nextToken` if there are more items to retrieve. + * @param input - The `ListPaginateInput` object. + * @returns A list of objects with key and metadata + * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket + * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials + */ +export function list(input?: ListPaginateInput): Promise; +/** + * @deprecated The `prefix` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/list | path} instead. + * List all files from S3 for a given `prefix`. You can set `listAll` to true in `options` to get all the files from S3. + * @param input - The `ListAllInput` object. + * @returns A list of all objects with key and metadata + * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket + * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials + */ +export function list(input?: ListAllInput): Promise; -export const list: ListApi = < - Output extends ListAllOutput | ListPaginateOutput, ->( - input?: ListAllInput | ListPaginateInput, -): Promise => listInternal(Amplify, input ?? {}) as Promise; +export function list( + input?: + | ListAllInput + | ListPaginateInput + | ListAllWithPathInput + | ListPaginateWithPathInput, +) { + return listInternal(Amplify, input ?? {}); +} diff --git a/packages/storage/src/providers/s3/apis/remove.ts b/packages/storage/src/providers/s3/apis/remove.ts index e0e100d2d4a..c0526df854c 100644 --- a/packages/storage/src/providers/s3/apis/remove.ts +++ b/packages/storage/src/providers/s3/apis/remove.ts @@ -5,38 +5,37 @@ import { Amplify } from '@aws-amplify/core'; import { RemoveInput, - RemoveInputWithKey, - RemoveInputWithPath, RemoveOutput, + RemoveWithPathInput, + RemoveWithPathOutput, } from '../types'; import { remove as removeInternal } from './internal/remove'; -interface RemoveApi { - /** - * Remove a file from your S3 bucket. - * @param input - The `RemoveInputWithPath` object. - * @return Output containing the removed object path. - * @throws service: `S3Exception` - S3 service errors thrown while while removing the object. - * @throws validation: `StorageValidationErrorCode` - Validation errors thrown - * when there is no path or path is empty or path has a leading slash. - */ - (input: RemoveInputWithPath): Promise; - /** - * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/remove | path} instead. - * - * Remove a file from your S3 bucket. - * @param input - The `RemoveInputWithKey` object. - * @return Output containing the removed object key - * @throws service: `S3Exception` - S3 service errors thrown while while removing the object - * @throws validation: `StorageValidationErrorCode` - Validation errors thrown - * when there is no key or its empty. - */ - (input: RemoveInputWithKey): Promise; - (input: RemoveInput): Promise; -} +/** + * Remove a file from your S3 bucket. + * @param input - The `RemoveWithPathInput` object. + * @return Output containing the removed object path. + * @throws service: `S3Exception` - S3 service errors thrown while while removing the object. + * @throws validation: `StorageValidationErrorCode` - Validation errors thrown + * when there is no path or path is empty or path has a leading slash. + */ +export function remove( + input: RemoveWithPathInput, +): Promise; +/** + * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/remove | path} instead. + * + * Remove a file from your S3 bucket. + * @param input - The `RemoveInput` object. + * @return Output containing the removed object key + * @throws service: `S3Exception` - S3 service errors thrown while while removing the object + * @throws validation: `StorageValidationErrorCode` - Validation errors thrown + * when there is no key or its empty. + */ +export function remove(input: RemoveInput): Promise; -export const remove: RemoveApi = ( - input: RemoveInput, -): Promise => removeInternal(Amplify, input) as Promise; +export function remove(input: RemoveInput | RemoveWithPathInput) { + return removeInternal(Amplify, input); +} diff --git a/packages/storage/src/providers/s3/apis/server/copy.ts b/packages/storage/src/providers/s3/apis/server/copy.ts index b241a808a58..e9486e10431 100644 --- a/packages/storage/src/providers/s3/apis/server/copy.ts +++ b/packages/storage/src/providers/s3/apis/server/copy.ts @@ -7,57 +7,48 @@ import { import { CopyInput, - CopyInputWithKey, - CopyInputWithPath, CopyOutput, + CopyWithPathInput, + CopyWithPathOutput, } from '../../types'; import { copy as copyInternal } from '../internal/copy'; -interface Copy { - /** - * Copy an object from a source to a destination object within the same bucket. - * - * @param contextSpec - The isolated server context. - * @param input - The CopyInputWithPath object. - * @returns Output containing the destination object path. - * @throws service: `S3Exception` - Thrown when checking for existence of the object - * @throws validation: `StorageValidationErrorCode` - Thrown when - * source or destination path is not defined. - */ - ( - contextSpec: AmplifyServer.ContextSpec, - input: CopyInputWithPath, - ): Promise; - /** - * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/copy | path} instead. - * - * Copy an object from a source to a destination object within the same bucket. Can optionally copy files across - * different accessLevel or identityId (if source object's accessLevel is 'protected'). - * - * @param contextSpec - The isolated server context. - * @param input - The CopyInputWithKey object. - * @returns Output containing the destination object key. - * @throws service: `S3Exception` - Thrown when checking for existence of the object - * @throws validation: `StorageValidationErrorCode` - Thrown when - * source or destination key is not defined. - */ - ( - contextSpec: AmplifyServer.ContextSpec, - input: CopyInputWithKey, - ): Promise; - - ( - contextSpec: AmplifyServer.ContextSpec, - input: CopyInput, - ): Promise; -} - -export const copy: Copy = ( +/** + * Copy an object from a source to a destination object within the same bucket. + * + * @param contextSpec - The isolated server context. + * @param input - The `CopyWithPathInput` object. + * @returns Output containing the destination object path. + * @throws service: `S3Exception` - Thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Thrown when + * source or destination path is not defined. + */ +export function copy( + contextSpec: AmplifyServer.ContextSpec, + input: CopyWithPathInput, +): Promise; +/** + * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/copy | path} instead. + * + * Copy an object from a source to a destination object within the same bucket. Can optionally copy files across + * different accessLevel or identityId (if source object's accessLevel is 'protected'). + * + * @param contextSpec - The isolated server context. + * @param input - The `CopyInput` object. + * @returns Output containing the destination object key. + * @throws service: `S3Exception` - Thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Thrown when + * source or destination key is not defined. + */ +export function copy( contextSpec: AmplifyServer.ContextSpec, input: CopyInput, -): Promise => - copyInternal( - getAmplifyServerContext(contextSpec).amplify, - input, - ) as Promise; +): Promise; + +export function copy( + contextSpec: AmplifyServer.ContextSpec, + input: CopyInput | CopyWithPathInput, +) { + return copyInternal(getAmplifyServerContext(contextSpec).amplify, input); +} diff --git a/packages/storage/src/providers/s3/apis/server/getProperties.ts b/packages/storage/src/providers/s3/apis/server/getProperties.ts index bca2c7aae98..87a77a297a4 100644 --- a/packages/storage/src/providers/s3/apis/server/getProperties.ts +++ b/packages/storage/src/providers/s3/apis/server/getProperties.ts @@ -8,57 +8,50 @@ import { import { GetPropertiesInput, - GetPropertiesInputWithKey, - GetPropertiesInputWithPath, GetPropertiesOutput, + GetPropertiesWithPathInput, + GetPropertiesWithPathOutput, } from '../../types'; import { getProperties as getPropertiesInternal } from '../internal/getProperties'; -interface GetProperties { - /** - * Gets the properties of a file. The properties include S3 system metadata and - * the user metadata that was provided when uploading the file. - * - * @param contextSpec - The isolated server context. - * @param input - The `GetPropertiesInputWithPath` object. - * @returns Requested object properties. - * @throws An `S3Exception` when the underlying S3 service returned error. - * @throws A `StorageValidationErrorCode` when API call parameters are invalid. - */ - ( - contextSpec: AmplifyServer.ContextSpec, - input: GetPropertiesInputWithPath, - ): Promise; - /** - * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/javascript/build-a-backend/storage/get-properties/ | path} instead. - * - * Gets the properties of a file. The properties include S3 system metadata and - * the user metadata that was provided when uploading the file. - * - * @param contextSpec - The isolated server context. - * @param input - The `GetPropertiesInputWithKey` object. - * @returns Requested object properties. - * @throws An `S3Exception` when the underlying S3 service returned error. - * @throws A `StorageValidationErrorCode` when API call parameters are invalid. - */ - ( - contextSpec: AmplifyServer.ContextSpec, - input: GetPropertiesInputWithKey, - ): Promise; - ( - contextSpec: AmplifyServer.ContextSpec, - input: GetPropertiesInput, - ): Promise; -} - -export const getProperties: GetProperties = < - Output extends GetPropertiesOutput, ->( +/** + * Gets the properties of a file. The properties include S3 system metadata and + * the user metadata that was provided when uploading the file. + * + * @param contextSpec - The isolated server context. + * @param input - The `GetPropertiesWithPathInput` object. + * @returns Requested object properties. + * @throws An `S3Exception` when the underlying S3 service returned error. + * @throws A `StorageValidationErrorCode` when API call parameters are invalid. + */ +export function getProperties( + contextSpec: AmplifyServer.ContextSpec, + input: GetPropertiesWithPathInput, +): Promise; +/** + * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/javascript/build-a-backend/storage/get-properties/ | path} instead. + * + * Gets the properties of a file. The properties include S3 system metadata and + * the user metadata that was provided when uploading the file. + * + * @param contextSpec - The isolated server context. + * @param input - The `GetPropertiesInput` object. + * @returns Requested object properties. + * @throws An `S3Exception` when the underlying S3 service returned error. + * @throws A `StorageValidationErrorCode` when API call parameters are invalid. + */ +export function getProperties( contextSpec: AmplifyServer.ContextSpec, input: GetPropertiesInput, -): Promise => - getPropertiesInternal( +): Promise; + +export function getProperties( + contextSpec: AmplifyServer.ContextSpec, + input: GetPropertiesInput | GetPropertiesWithPathInput, +) { + return getPropertiesInternal( getAmplifyServerContext(contextSpec).amplify, input, - ) as Promise; + ); +} diff --git a/packages/storage/src/providers/s3/apis/server/getUrl.ts b/packages/storage/src/providers/s3/apis/server/getUrl.ts index d3a75a1299e..f9f4e80d07c 100644 --- a/packages/storage/src/providers/s3/apis/server/getUrl.ts +++ b/packages/storage/src/providers/s3/apis/server/getUrl.ts @@ -8,65 +8,61 @@ import { import { GetUrlInput, - GetUrlInputWithKey, - GetUrlInputWithPath, GetUrlOutput, + GetUrlWithPathInput, + GetUrlWithPathOutput, } from '../../types'; import { getUrl as getUrlInternal } from '../internal/getUrl'; -interface GetUrl { - /** - * Get a temporary presigned URL to download the specified S3 object. - * The presigned URL expires when the associated role used to sign the request expires or - * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. - * - * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` - * to true, this method will verify the given object already exists in S3 before returning a presigned - * URL, and will throw `StorageError` if the object does not exist. - * - * @param contextSpec - The isolated server context. - * @param input - The `GetUrlInputWithPath` object. - * @returns Presigned URL and timestamp when the URL MAY expire. - * @throws service: `S3Exception` - thrown when checking for existence of the object - * @throws validation: `StorageValidationErrorCode` - Validation errors - * thrown either username or key are not defined. - * - */ - ( - contextSpec: AmplifyServer.ContextSpec, - input: GetUrlInputWithPath, - ): Promise; - /** - * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/javascript/build-a-backend/storage/download/#generate-a-download-url | path} instead. - * - * Get a temporary presigned URL to download the specified S3 object. - * The presigned URL expires when the associated role used to sign the request expires or - * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. - * - * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` - * to true, this method will verify the given object already exists in S3 before returning a presigned - * URL, and will throw `StorageError` if the object does not exist. - * - * @param contextSpec - The isolated server context. - * @param input - The `GetUrlInputWithKey` object. - * @returns Presigned URL and timestamp when the URL MAY expire. - * @throws service: `S3Exception` - thrown when checking for existence of the object - * @throws validation: `StorageValidationErrorCode` - Validation errors - * thrown either username or key are not defined. - * - */ - ( - contextSpec: AmplifyServer.ContextSpec, - input: GetUrlInputWithKey, - ): Promise; - ( - contextSpec: AmplifyServer.ContextSpec, - input: GetUrlInput, - ): Promise; -} -export const getUrl: GetUrl = async ( +/** + * Get a temporary presigned URL to download the specified S3 object. + * The presigned URL expires when the associated role used to sign the request expires or + * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. + * + * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` + * to true, this method will verify the given object already exists in S3 before returning a presigned + * URL, and will throw `StorageError` if the object does not exist. + * + * @param contextSpec - The isolated server context. + * @param input - The `GetUrlWithPathInput` object. + * @returns Presigned URL and timestamp when the URL may expire. + * @throws service: `S3Exception` - thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Validation errors + * thrown either username or key are not defined. + * + */ +export function getUrl( + contextSpec: AmplifyServer.ContextSpec, + input: GetUrlWithPathInput, +): Promise; +/** + * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/javascript/build-a-backend/storage/download/#generate-a-download-url | path} instead. + * + * Get a temporary presigned URL to download the specified S3 object. + * The presigned URL expires when the associated role used to sign the request expires or + * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. + * + * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` + * to true, this method will verify the given object already exists in S3 before returning a presigned + * URL, and will throw `StorageError` if the object does not exist. + * + * @param contextSpec - The isolated server context. + * @param input - The `GetUrlInput` object. + * @returns Presigned URL and timestamp when the URL may expire. + * @throws service: `S3Exception` - thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Validation errors + * thrown either username or key are not defined. + * + */ +export function getUrl( contextSpec: AmplifyServer.ContextSpec, input: GetUrlInput, -): Promise => - getUrlInternal(getAmplifyServerContext(contextSpec).amplify, input); +): Promise; + +export function getUrl( + contextSpec: AmplifyServer.ContextSpec, + input: GetUrlInput | GetUrlWithPathInput, +) { + return getUrlInternal(getAmplifyServerContext(contextSpec).amplify, input); +} diff --git a/packages/storage/src/providers/s3/apis/server/list.ts b/packages/storage/src/providers/s3/apis/server/list.ts index fe1a4e5f885..66d0ad4cd22 100644 --- a/packages/storage/src/providers/s3/apis/server/list.ts +++ b/packages/storage/src/providers/s3/apis/server/list.ts @@ -7,86 +7,79 @@ import { import { ListAllInput, - ListAllInputWithPath, - ListAllInputWithPrefix, ListAllOutput, + ListAllWithPathInput, + ListAllWithPathOutput, ListPaginateInput, - ListPaginateInputWithPath, - ListPaginateInputWithPrefix, ListPaginateOutput, + ListPaginateWithPathInput, + ListPaginateWithPathOutput, } from '../../types'; import { list as listInternal } from '../internal/list'; -interface ListApi { - /** - * List files in pages with the given `path`. - * `pageSize` is defaulted to 1000. Additionally, the result will include a `nextToken` if there are more items to retrieve. - * @param input - The `ListPaginateInputWithPath` object. - * @param contextSpec - The context spec used to get the Amplify server context. - * @returns A list of objects with path and metadata - * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket - * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials - */ - ( - contextSpec: AmplifyServer.ContextSpec, - input: ListPaginateInputWithPath, - ): Promise; - /** - * List all files from S3 for a given `path`. You can set `listAll` to true in `options` to get all the files from S3. - * @param input - The `ListAllInputWithPath` object. - * @param contextSpec - The context spec used to get the Amplify server context. - * @returns A list of all objects with path and metadata - * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket - * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials - */ - ( - contextSpec: AmplifyServer.ContextSpec, - input: ListAllInputWithPath, - ): Promise; - /** - * @deprecated The `prefix` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/list | path} instead. - * List files in pages with the given `prefix`. - * `pageSize` is defaulted to 1000. Additionally, the result will include a `nextToken` if there are more items to retrieve. - * @param input - The `ListPaginateInputWithPrefix` object. - * @returns A list of objects with key and metadata - * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket - * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials - */ - ( - contextSpec: AmplifyServer.ContextSpec, - input?: ListPaginateInputWithPrefix, - ): Promise; - /** - * @deprecated The `prefix` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/list | path} instead. - * List all files from S3 for a given `prefix`. You can set `listAll` to true in `options` to get all the files from S3. - * @param input - The `ListAllInputWithPrefix` object. - * @returns A list of all objects with key and metadata - * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket - * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials - */ - ( - contextSpec: AmplifyServer.ContextSpec, - input?: ListAllInputWithPrefix, - ): Promise; - ( - contextSpec: AmplifyServer.ContextSpec, - input?: ListPaginateInput, - ): Promise; - ( - contextSpec: AmplifyServer.ContextSpec, - input?: ListAllInput, - ): Promise; -} +/** + * List files in pages with the given `path`. + * `pageSize` is defaulted to 1000. Additionally, the result will include a `nextToken` if there are more items to retrieve. + * @param input - The `ListPaginateWithPathInput` object. + * @param contextSpec - The context spec used to get the Amplify server context. + * @returns A list of objects with path and metadata + * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket + * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials + */ +export function list( + contextSpec: AmplifyServer.ContextSpec, + input: ListPaginateWithPathInput, +): Promise; +/** + * List all files from S3 for a given `path`. You can set `listAll` to true in `options` to get all the files from S3. + * @param input - The `ListAllWithPathInput` object. + * @param contextSpec - The context spec used to get the Amplify server context. + * @returns A list of all objects with path and metadata + * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket + * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials + */ +export function list( + contextSpec: AmplifyServer.ContextSpec, + input: ListAllWithPathInput, +): Promise; +/** + * @deprecated The `prefix` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/list | path} instead. + * List files in pages with the given `prefix`. + * `pageSize` is defaulted to 1000. Additionally, the result will include a `nextToken` if there are more items to retrieve. + * @param input - The `ListPaginateInput` object. + * @returns A list of objects with key and metadata + * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket + * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials + */ +export function list( + contextSpec: AmplifyServer.ContextSpec, + input?: ListPaginateInput, +): Promise; +/** + * @deprecated The `prefix` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/list | path} instead. + * List all files from S3 for a given `prefix`. You can set `listAll` to true in `options` to get all the files from S3. + * @param input - The `ListAllInput` object. + * @returns A list of all objects with key and metadata + * @throws service: `S3Exception` - S3 service errors thrown when checking for existence of bucket + * @throws validation: `StorageValidationErrorCode` - thrown when there are issues with credentials + */ +export function list( + contextSpec: AmplifyServer.ContextSpec, + input?: ListAllInput, +): Promise; -export const list: ListApi = < - Output extends ListAllOutput | ListPaginateOutput, ->( +export function list( contextSpec: AmplifyServer.ContextSpec, - input?: ListAllInput | ListPaginateInput, -): Promise => - listInternal( + input?: + | ListAllInput + | ListPaginateInput + | ListAllWithPathInput + | ListPaginateWithPathInput, +) { + return listInternal( getAmplifyServerContext(contextSpec).amplify, input ?? {}, - ) as Promise; + ); +} diff --git a/packages/storage/src/providers/s3/apis/server/remove.ts b/packages/storage/src/providers/s3/apis/server/remove.ts index 0815f1c1c44..5b788447f64 100644 --- a/packages/storage/src/providers/s3/apis/server/remove.ts +++ b/packages/storage/src/providers/s3/apis/server/remove.ts @@ -8,53 +8,45 @@ import { import { RemoveInput, - RemoveInputWithKey, - RemoveInputWithPath, RemoveOutput, + RemoveWithPathInput, + RemoveWithPathOutput, } from '../../types'; import { remove as removeInternal } from '../internal/remove'; -interface RemoveApi { - /** - * Remove a file from your S3 bucket. - * @param input - The `RemoveInputWithPath` object. - * @param contextSpec - The context spec used to get the Amplify server context. - * @return Output containing the removed object path. - * @throws service: `S3Exception` - S3 service errors thrown while while removing the object. - * @throws validation: `StorageValidationErrorCode` - Validation errors thrown - * when there is no path or path is empty or path has a leading slash. - */ - ( - contextSpec: AmplifyServer.ContextSpec, - input: RemoveInputWithPath, - ): Promise; - /** - * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. - * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/remove | path} instead. - * - * Remove a file from your S3 bucket. - * @param input - The `RemoveInputWithKey` object. - * @param contextSpec - The context spec used to get the Amplify server context. - * @return Output containing the removed object key - * @throws service: `S3Exception` - S3 service errors thrown while while removing the object - * @throws validation: `StorageValidationErrorCode` - Validation errors thrown - * when there is no key or its empty. - */ - ( - contextSpec: AmplifyServer.ContextSpec, - input: RemoveInputWithKey, - ): Promise; - ( - contextSpec: AmplifyServer.ContextSpec, - input: RemoveInput, - ): Promise; -} - -export const remove: RemoveApi = ( +/** + * Remove a file from your S3 bucket. + * @param input - The `RemoveWithPathInput` object. + * @param contextSpec - The context spec used to get the Amplify server context. + * @return Output containing the removed object path. + * @throws service: `S3Exception` - S3 service errors thrown while while removing the object. + * @throws validation: `StorageValidationErrorCode` - Validation errors thrown + * when there is no path or path is empty or path has a leading slash. + */ +export function remove( + contextSpec: AmplifyServer.ContextSpec, + input: RemoveWithPathInput, +): Promise; +/** + * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/remove | path} instead. + * + * Remove a file from your S3 bucket. + * @param input - The `RemoveInput` object. + * @param contextSpec - The context spec used to get the Amplify server context. + * @return Output containing the removed object key + * @throws service: `S3Exception` - S3 service errors thrown while while removing the object + * @throws validation: `StorageValidationErrorCode` - Validation errors thrown + * when there is no key or its empty. + */ +export function remove( contextSpec: AmplifyServer.ContextSpec, input: RemoveInput, -): Promise => - removeInternal( - getAmplifyServerContext(contextSpec).amplify, - input, - ) as Promise; +): Promise; + +export function remove( + contextSpec: AmplifyServer.ContextSpec, + input: RemoveInput | RemoveWithPathInput, +) { + return removeInternal(getAmplifyServerContext(contextSpec).amplify, input); +} diff --git a/packages/storage/src/providers/s3/apis/uploadData/index.ts b/packages/storage/src/providers/s3/apis/uploadData/index.ts index 0bf632996a3..8669309ec53 100644 --- a/packages/storage/src/providers/s3/apis/uploadData/index.ts +++ b/packages/storage/src/providers/s3/apis/uploadData/index.ts @@ -3,9 +3,9 @@ import { UploadDataInput, - UploadDataInputWithKey, - UploadDataInputWithPath, UploadDataOutput, + UploadDataWithPathInput, + UploadDataWithPathOutput, } from '../../types'; import { createUploadTask } from '../../utils'; import { assertValidationError } from '../../../../errors/utils/assertValidationError'; @@ -16,120 +16,117 @@ import { byteLength } from './byteLength'; import { putObjectJob } from './putObjectJob'; import { getMultipartUploadHandlers } from './multipart'; -interface UploadData { - /** - * Upload data to the specified S3 object path. By default uses single PUT operation to upload if the payload is less than 5MB. - * Otherwise, uses multipart upload to upload the payload. If the payload length cannot be determined, uses multipart upload. - * - * Limitations: - * * Maximum object size is 5TB. - * * Maximum object size if the size cannot be determined before upload is 50GB. - * - * @throws Service: `S3Exception` thrown when checking for existence of the object. - * @throws Validation: `StorageValidationErrorCode` thrown when a validation error occurs. - * - * @param input - A `UploadDataInputWithPath` object. - * - * @returns A cancelable and resumable task exposing result promise from `result` - * property. - * - * @example - * ```ts - * // Upload a file to s3 bucket - * await uploadData({ path, data: file, options: { - * onProgress, // Optional progress callback. - * } }).result; - * ``` - * - * @example - * ```ts - * // Cancel a task - * const uploadTask = uploadData({ path, data: file }); - * //... - * uploadTask.cancel(); - * try { - * await uploadTask.result; - * } catch (error) { - * if(isCancelError(error)) { - * // Handle error thrown by task cancelation. - * } - * } - *``` - * - * @example - * ```ts - * // Pause and resume a task - * const uploadTask = uploadData({ path, data: file }); - * //... - * uploadTask.pause(); - * //... - * uploadTask.resume(); - * //... - * await uploadTask.result; - * ``` - */ - (input: UploadDataInputWithPath): UploadDataOutput; +/** + * Upload data to the specified S3 object path. By default uses single PUT operation to upload if the payload is less than 5MB. + * Otherwise, uses multipart upload to upload the payload. If the payload length cannot be determined, uses multipart upload. + * + * Limitations: + * * Maximum object size is 5TB. + * * Maximum object size if the size cannot be determined before upload is 50GB. + * + * @throws Service: `S3Exception` thrown when checking for existence of the object. + * @throws Validation: `StorageValidationErrorCode` thrown when a validation error occurs. + * + * @param input - A `UploadDataWithPathInput` object. + * + * @returns A cancelable and resumable task exposing result promise from `result` + * property. + * + * @example + * ```ts + * // Upload a file to s3 bucket + * await uploadData({ path, data: file, options: { + * onProgress, // Optional progress callback. + * } }).result; + * ``` + * + * @example + * ```ts + * // Cancel a task + * const uploadTask = uploadData({ path, data: file }); + * //... + * uploadTask.cancel(); + * try { + * await uploadTask.result; + * } catch (error) { + * if(isCancelError(error)) { + * // Handle error thrown by task cancelation. + * } + * } + *``` + * + * @example + * ```ts + * // Pause and resume a task + * const uploadTask = uploadData({ path, data: file }); + * //... + * uploadTask.pause(); + * //... + * uploadTask.resume(); + * //... + * await uploadTask.result; + * ``` + */ +export function uploadData( + input: UploadDataWithPathInput, +): UploadDataWithPathOutput; - /** - * Upload data to the specified S3 object key. By default uses single PUT operation to upload if the payload is less than 5MB. - * Otherwise, uses multipart upload to upload the payload. If the payload length cannot be determined, uses multipart upload. - * - * Limitations: - * * Maximum object size is 5TB. - * * Maximum object size if the size cannot be determined before upload is 50GB. - * - * @deprecated The `key` and `accessLevel` parameters are deprecated and will be removed in next major version. - * Please use {@link https://docs.amplify.aws/javascript/build-a-backend/storage/upload/#uploaddata | path} instead. - * - * @throws Service: `S3Exception` thrown when checking for existence of the object. - * @throws Validation: `StorageValidationErrorCode` thrown when a validation error occurs. - * - * @param input - A UploadDataInputWithKey object. - * - * @returns A cancelable and resumable task exposing result promise from the `result` property. - * - * @example - * ```ts - * // Upload a file to s3 bucket - * await uploadData({ key, data: file, options: { - * onProgress, // Optional progress callback. - * } }).result; - * ``` - * - * @example - * ```ts - * // Cancel a task - * const uploadTask = uploadData({ key, data: file }); - * //... - * uploadTask.cancel(); - * try { - * await uploadTask.result; - * } catch (error) { - * if(isCancelError(error)) { - * // Handle error thrown by task cancelation. - * } - * } - *``` - * - * @example - * ```ts - * // Pause and resume a task - * const uploadTask = uploadData({ key, data: file }); - * //... - * uploadTask.pause(); - * //... - * uploadTask.resume(); - * //... - * await uploadTask.result; - * ``` - */ - (input: UploadDataInputWithKey): UploadDataOutput; - (input: UploadDataInput): UploadDataOutput; -} +/** + * Upload data to the specified S3 object key. By default uses single PUT operation to upload if the payload is less than 5MB. + * Otherwise, uses multipart upload to upload the payload. If the payload length cannot be determined, uses multipart upload. + * + * Limitations: + * * Maximum object size is 5TB. + * * Maximum object size if the size cannot be determined before upload is 50GB. + * + * @deprecated The `key` and `accessLevel` parameters are deprecated and will be removed in next major version. + * Please use {@link https://docs.amplify.aws/javascript/build-a-backend/storage/upload/#uploaddata | path} instead. + * + * @throws Service: `S3Exception` thrown when checking for existence of the object. + * @throws Validation: `StorageValidationErrorCode` thrown when a validation error occurs. + * + * @param input - A `UploadDataInput` object. + * + * @returns A cancelable and resumable task exposing result promise from the `result` property. + * + * @example + * ```ts + * // Upload a file to s3 bucket + * await uploadData({ key, data: file, options: { + * onProgress, // Optional progress callback. + * } }).result; + * ``` + * + * @example + * ```ts + * // Cancel a task + * const uploadTask = uploadData({ key, data: file }); + * //... + * uploadTask.cancel(); + * try { + * await uploadTask.result; + * } catch (error) { + * if(isCancelError(error)) { + * // Handle error thrown by task cancelation. + * } + * } + *``` + * + * @example + * ```ts + * // Pause and resume a task + * const uploadTask = uploadData({ key, data: file }); + * //... + * uploadTask.pause(); + * //... + * uploadTask.resume(); + * //... + * await uploadTask.result; + * ``` + */ +export function uploadData(input: UploadDataInput): UploadDataOutput; -export const uploadData: UploadData = ( - input: UploadDataInput, -): Output => { +export function uploadData(input: UploadDataInput | UploadDataWithPathInput) { const { data } = input; const dataByteLength = byteLength(data); @@ -148,7 +145,7 @@ export const uploadData: UploadData = ( onCancel: (message?: string) => { abortController.abort(message); }, - }) as Output; + }); } else { // Multipart upload const { multipartUploadJob, onPause, onResume, onCancel } = @@ -162,6 +159,6 @@ export const uploadData: UploadData = ( }, onPause, onResume, - }) as Output; + }); } -}; +} diff --git a/packages/storage/src/providers/s3/apis/uploadData/multipart/uploadHandlers.ts b/packages/storage/src/providers/s3/apis/uploadData/multipart/uploadHandlers.ts index e0100107492..e216feeede7 100644 --- a/packages/storage/src/providers/s3/apis/uploadData/multipart/uploadHandlers.ts +++ b/packages/storage/src/providers/s3/apis/uploadData/multipart/uploadHandlers.ts @@ -4,12 +4,12 @@ import { Amplify, StorageAccessLevel } from '@aws-amplify/core'; import { StorageAction } from '@aws-amplify/core/internals/utils'; -import { UploadDataInput } from '../../../types'; +import { UploadDataInput, UploadDataWithPathInput } from '../../../types'; import { resolveS3ConfigAndInput, validateStorageOperationInput, } from '../../../utils'; -import { ItemWithKeyAndPath } from '../../../types/outputs'; +import { ItemWithKey, ItemWithPath } from '../../../types/outputs'; import { DEFAULT_ACCESS_LEVEL, DEFAULT_QUEUE_SIZE, @@ -43,10 +43,12 @@ import { getDataChunker } from './getDataChunker'; * @internal */ export const getMultipartUploadHandlers = ( - uploadDataInput: UploadDataInput, + uploadDataInput: UploadDataInput | UploadDataWithPathInput, size?: number, ) => { - let resolveCallback: ((value: ItemWithKeyAndPath) => void) | undefined; + let resolveCallback: + | ((value: ItemWithKey | ItemWithPath) => void) + | undefined; let rejectCallback: ((reason?: any) => void) | undefined; let inProgressUpload: | { @@ -67,7 +69,7 @@ export const getMultipartUploadHandlers = ( // This should be replaced by a special abort reason. However,the support of this API is lagged behind. let isAbortSignalFromPause = false; - const startUpload = async (): Promise => { + const startUpload = async (): Promise => { const { options: uploadDataOptions, data } = uploadDataInput; const resolvedS3Options = await resolveS3ConfigAndInput( Amplify, @@ -218,8 +220,8 @@ export const getMultipartUploadHandlers = ( }; return inputType === STORAGE_INPUT_KEY - ? { key: objectKey, path: finalKey, ...result } - : { path: finalKey, key: finalKey, ...result }; + ? { key: objectKey, ...result } + : { path: objectKey, ...result }; }; const startUploadWithResumability = () => @@ -236,7 +238,7 @@ export const getMultipartUploadHandlers = ( }); const multipartUploadJob = () => - new Promise((resolve, reject) => { + new Promise((resolve, reject) => { resolveCallback = resolve; rejectCallback = reject; startUploadWithResumability(); diff --git a/packages/storage/src/providers/s3/apis/uploadData/putObjectJob.ts b/packages/storage/src/providers/s3/apis/uploadData/putObjectJob.ts index 81b2fe6fa94..bb9b5ec4519 100644 --- a/packages/storage/src/providers/s3/apis/uploadData/putObjectJob.ts +++ b/packages/storage/src/providers/s3/apis/uploadData/putObjectJob.ts @@ -4,13 +4,13 @@ import { Amplify } from '@aws-amplify/core'; import { StorageAction } from '@aws-amplify/core/internals/utils'; -import { UploadDataInput } from '../../types'; +import { UploadDataInput, UploadDataWithPathInput } from '../../types'; import { calculateContentMd5, resolveS3ConfigAndInput, validateStorageOperationInput, } from '../../utils'; -import { ItemWithKeyAndPath } from '../../types/outputs'; +import { ItemWithKey, ItemWithPath } from '../../types/outputs'; import { putObject } from '../../utils/client'; import { getStorageUserAgentValue } from '../../utils/userAgent'; import { STORAGE_INPUT_KEY } from '../../utils/constants'; @@ -22,11 +22,11 @@ import { STORAGE_INPUT_KEY } from '../../utils/constants'; */ export const putObjectJob = ( - uploadDataInput: UploadDataInput, + uploadDataInput: UploadDataInput | UploadDataWithPathInput, abortSignal: AbortSignal, totalLength?: number, ) => - async (): Promise => { + async (): Promise => { const { options: uploadDataOptions, data } = uploadDataInput; const { bucket, keyPrefix, s3Config, isObjectLockEnabled, identityId } = await resolveS3ConfigAndInput(Amplify, uploadDataOptions); @@ -75,6 +75,6 @@ export const putObjectJob = }; return inputType === STORAGE_INPUT_KEY - ? { key: objectKey, path: finalKey, ...result } - : { path: finalKey, key: finalKey, ...result }; + ? { key: objectKey, ...result } + : { path: objectKey, ...result }; }; diff --git a/packages/storage/src/providers/s3/index.ts b/packages/storage/src/providers/s3/index.ts index dd2f2eb015e..2ec8bb61527 100644 --- a/packages/storage/src/providers/s3/index.ts +++ b/packages/storage/src/providers/s3/index.ts @@ -13,22 +13,38 @@ export { export { UploadDataInput, + UploadDataWithPathInput, DownloadDataInput, + DownloadDataWithPathInput, RemoveInput, + RemoveWithPathInput, ListAllInput, + ListAllWithPathInput, ListPaginateInput, + ListPaginateWithPathInput, GetPropertiesInput, + GetPropertiesWithPathInput, CopyInput, + CopyWithPathInput, GetUrlInput, + GetUrlWithPathInput, } from './types/inputs'; export { UploadDataOutput, + UploadDataWithPathOutput, DownloadDataOutput, + DownloadDataWithPathOutput, RemoveOutput, + RemoveWithPathOutput, ListAllOutput, + ListAllWithPathOutput, ListPaginateOutput, + ListPaginateWithPathOutput, GetPropertiesOutput, + GetPropertiesWithPathOutput, CopyOutput, + CopyWithPathOutput, GetUrlOutput, + GetUrlWithPathOutput, } from './types/outputs'; diff --git a/packages/storage/src/providers/s3/types/index.ts b/packages/storage/src/providers/s3/types/index.ts index 17c0a7dd28d..4299687cd8e 100644 --- a/packages/storage/src/providers/s3/types/index.ts +++ b/packages/storage/src/providers/s3/types/index.ts @@ -19,40 +19,41 @@ export { CopySourceOptionsWithKey, } from './options'; export { - DownloadDataOutput, - GetUrlOutput, UploadDataOutput, + UploadDataWithPathOutput, + DownloadDataOutput, + DownloadDataWithPathOutput, + RemoveOutput, + RemoveWithPathOutput, ListAllOutput, + ListAllWithPathOutput, ListPaginateOutput, + ListPaginateWithPathOutput, GetPropertiesOutput, + GetPropertiesWithPathOutput, CopyOutput, - RemoveOutput, - ItemWithKeyAndPath, + CopyWithPathOutput, + GetUrlOutput, + GetUrlWithPathOutput, + ListOutputItem, + ListOutputItemWithPath, } from './outputs'; export { CopyInput, - CopyInputWithKey, - CopyInputWithPath, + CopyWithPathInput, GetPropertiesInput, - GetPropertiesInputWithKey, - GetPropertiesInputWithPath, + GetPropertiesWithPathInput, GetUrlInput, - GetUrlInputWithKey, - GetUrlInputWithPath, - RemoveInputWithKey, - RemoveInputWithPath, + GetUrlWithPathInput, + RemoveWithPathInput, RemoveInput, DownloadDataInput, - DownloadDataInputWithKey, - DownloadDataInputWithPath, + DownloadDataWithPathInput, UploadDataInput, - UploadDataInputWithPath, - UploadDataInputWithKey, + UploadDataWithPathInput, ListAllInput, ListPaginateInput, - ListAllInputWithPath, - ListPaginateInputWithPath, - ListAllInputWithPrefix, - ListPaginateInputWithPrefix, + ListAllWithPathInput, + ListPaginateWithPathInput, } from './inputs'; export { S3Exception } from './errors'; diff --git a/packages/storage/src/providers/s3/types/inputs.ts b/packages/storage/src/providers/s3/types/inputs.ts index fa16e636b49..f7bd6c5db44 100644 --- a/packages/storage/src/providers/s3/types/inputs.ts +++ b/packages/storage/src/providers/s3/types/inputs.ts @@ -1,8 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { StrictUnion } from '@aws-amplify/core/internals/utils'; - import { StorageCopyInputWithKey, StorageCopyInputWithPath, @@ -39,120 +37,101 @@ import { // TODO: support use accelerate endpoint option /** + * @deprecated Use {@link CopyWithPathInput} instead. * Input type for S3 copy API. */ -export type CopyInput = CopyInputWithKey | CopyInputWithPath; - -/** @deprecated Use {@link CopyInputWithPath} instead. */ -export type CopyInputWithKey = StorageCopyInputWithKey< +export type CopyInput = StorageCopyInputWithKey< CopySourceOptionsWithKey, CopyDestinationOptionsWithKey >; -export type CopyInputWithPath = StorageCopyInputWithPath; +/** + * Input type with path for S3 copy API. + */ +export type CopyWithPathInput = StorageCopyInputWithPath; /** + * @deprecated Use {@link GetPropertiesWithPathInput} instead. * Input type for S3 getProperties API. */ -export type GetPropertiesInput = StrictUnion< - GetPropertiesInputWithKey | GetPropertiesInputWithPath ->; - -/** @deprecated Use {@link GetPropertiesInputWithPath} instead. */ -export type GetPropertiesInputWithKey = +export type GetPropertiesInput = StorageGetPropertiesInputWithKey; -export type GetPropertiesInputWithPath = +/** + * Input type with for S3 getProperties API. + */ +export type GetPropertiesWithPathInput = StorageGetPropertiesInputWithPath; /** + * @deprecated Use {@link GetUrlWithPathInput} instead. * Input type for S3 getUrl API. */ -export type GetUrlInput = StrictUnion; - -/** @deprecated Use {@link GetUrlInputWithPath} instead. */ -export type GetUrlInputWithKey = - StorageGetUrlInputWithKey; -export type GetUrlInputWithPath = - StorageGetUrlInputWithPath; - +export type GetUrlInput = StorageGetUrlInputWithKey; /** - * Input type for S3 list API. Lists all bucket objects. + * Input type with path for S3 getUrl API. */ -export type ListAllInput = StrictUnion< - ListAllInputWithPath | ListAllInputWithPrefix ->; - -/** - * Input type for S3 list API. Lists bucket objects with pagination. - */ -export type ListPaginateInput = StrictUnion< - ListPaginateInputWithPath | ListPaginateInputWithPrefix ->; +export type GetUrlWithPathInput = + StorageGetUrlInputWithPath; /** - * Input type for S3 list API. Lists all bucket objects. + * Input type with path for S3 list API. Lists all bucket objects. */ -export type ListAllInputWithPath = +export type ListAllWithPathInput = StorageListInputWithPath; /** - * Input type for S3 list API. Lists bucket objects with pagination. + * Input type with path for S3 list API. Lists bucket objects with pagination. */ -export type ListPaginateInputWithPath = +export type ListPaginateWithPathInput = StorageListInputWithPath; /** - * @deprecated Use {@link ListAllInputWithPath} instead. + * @deprecated Use {@link ListAllWithPathInput} instead. * Input type for S3 list API. Lists all bucket objects. */ -export type ListAllInputWithPrefix = - StorageListInputWithPrefix; +export type ListAllInput = StorageListInputWithPrefix; /** - * @deprecated Use {@link ListPaginateInputWithPath} instead. + * @deprecated Use {@link ListPaginateWithPathInput} instead. * Input type for S3 list API. Lists bucket objects with pagination. */ -export type ListPaginateInputWithPrefix = +export type ListPaginateInput = StorageListInputWithPrefix; /** - * @deprecated Use {@link RemoveInputWithPath} instead. + * @deprecated Use {@link RemoveWithPathInput} instead. * Input type with key for S3 remove API. */ -export type RemoveInputWithKey = StorageRemoveInputWithKey; +export type RemoveInput = StorageRemoveInputWithKey; /** * Input type with path for S3 remove API. */ -export type RemoveInputWithPath = StorageRemoveInputWithPath< +export type RemoveWithPathInput = StorageRemoveInputWithPath< Omit >; /** - * Input type for S3 remove API. + * @deprecated Use {@link DownloadDataWithPathInput} instead. + * Input type for S3 downloadData API. */ -export type RemoveInput = StrictUnion; +export type DownloadDataInput = + StorageDownloadDataInputWithKey; /** - * Input type for S3 downloadData API. + * Input type with path for S3 downloadData API. */ -export type DownloadDataInput = StrictUnion< - DownloadDataInputWithKey | DownloadDataInputWithPath ->; -/** @deprecated Use {@link DownloadDataInputWithPath} instead. */ -export type DownloadDataInputWithKey = - StorageDownloadDataInputWithKey; -export type DownloadDataInputWithPath = +export type DownloadDataWithPathInput = StorageDownloadDataInputWithPath; /** + * @deprecated Use {@link UploadDataWithPathInput} instead. * Input type for S3 uploadData API. */ -export type UploadDataInput = StrictUnion< - UploadDataInputWithKey | UploadDataInputWithPath ->; - -/** @deprecated Use {@link UploadDataInputWithPath} instead. */ -export type UploadDataInputWithKey = +export type UploadDataInput = StorageUploadDataInputWithKey; -export type UploadDataInputWithPath = + +/** + * Input type with path for S3 uploadData API. + */ +export type UploadDataWithPathInput = StorageUploadDataInputWithPath; diff --git a/packages/storage/src/providers/s3/types/outputs.ts b/packages/storage/src/providers/s3/types/outputs.ts index 5d4b7f484a7..44524536a3b 100644 --- a/packages/storage/src/providers/s3/types/outputs.ts +++ b/packages/storage/src/providers/s3/types/outputs.ts @@ -5,7 +5,8 @@ import { DownloadTask, StorageDownloadDataOutput, StorageGetUrlOutput, - StorageItem, + StorageItemWithKey, + StorageItemWithPath, StorageListOutput, UploadTask, } from '../../../types'; @@ -25,52 +26,114 @@ export interface ItemBase { } /** - * type for S3 list item. + * @deprecated Use {@link ListOutputItemWithPath} instead. + * type for S3 list item with key. */ -export type ListOutputItem = Omit; +export type ListOutputItem = Omit; -export type ItemWithKeyAndPath = ItemBase & StorageItem; +/** + * type for S3 list item with path. + */ +export type ListOutputItemWithPath = Omit; + +/** + * @deprecated Use {@link ItemWithPath} instead. + */ +export type ItemWithKey = ItemBase & StorageItemWithKey; + +/** + * type for S3 list item with path. + */ +export type ItemWithPath = ItemBase & StorageItemWithPath; /** * Output type for S3 downloadData API. + * @deprecated Use {@link DownloadDataWithPathOutput} instead. */ export type DownloadDataOutput = DownloadTask< - StorageDownloadDataOutput + StorageDownloadDataOutput >; - /** - * Output type for S3 uploadData API. + * Output type with path for S3 downloadData API. */ -export type UploadDataOutput = UploadTask; +export type DownloadDataWithPathOutput = DownloadTask< + StorageDownloadDataOutput +>; /** * Output type for S3 getUrl API. + * @deprecated Use {@link GetUrlWithPathOutput} instead. */ export type GetUrlOutput = StorageGetUrlOutput; - /** - * Output type for S3 getProperties API. - */ -export type GetPropertiesOutput = ItemWithKeyAndPath; + * Output type with path for S3 getUrl API. + * */ +export type GetUrlWithPathOutput = StorageGetUrlOutput; /** - * Output type for S3 Copy API. + * Output type for S3 uploadData API. + * @deprecated Use {@link UploadDataWithPathOutput} instead. */ -export type CopyOutput = Pick; +export type UploadDataOutput = UploadTask; +/** + * Output type with path for S3 uploadData API. + * */ +export type UploadDataWithPathOutput = UploadTask; /** - * Output type for S3 remove API. - */ -export type RemoveOutput = Pick; + * Output type for S3 getProperties API. + * @deprecated Use {@link GetPropertiesWithPathOutput} instead. + * */ +export type GetPropertiesOutput = ItemBase & StorageItemWithKey; +/** + * Output type with path for S3 getProperties API. + * */ +export type GetPropertiesWithPathOutput = ItemBase & StorageItemWithPath; /** + * @deprecated Use {@link ListAllWithPathOutput} instead. * Output type for S3 list API. Lists all bucket objects. */ export type ListAllOutput = StorageListOutput; /** + * Output type with path for S3 list API. Lists all bucket objects. + */ +export type ListAllWithPathOutput = StorageListOutput; + +/** + * @deprecated Use {@link ListPaginateWithPathOutput} instead. * Output type for S3 list API. Lists bucket objects with pagination. */ export type ListPaginateOutput = StorageListOutput & { nextToken?: string; }; + +/** + * Output type with path for S3 list API. Lists bucket objects with pagination. + */ +export type ListPaginateWithPathOutput = + StorageListOutput & { + nextToken?: string; + }; + +/** + * Output type with path for S3 copy API. + * @deprecated Use {@link CopyWithPathOutput} instead. + */ +export type CopyOutput = Pick; +/** + * Output type with path for S3 copy API. + */ +export type CopyWithPathOutput = Pick; + +/** + * @deprecated Use {@link RemoveWithPathOutput} instead. + * Output type with key for S3 remove API. + */ +export type RemoveOutput = Pick; + +/** + * Output type with path for S3 remove API. + */ +export type RemoveWithPathOutput = Pick; diff --git a/packages/storage/src/types/index.ts b/packages/storage/src/types/index.ts index 311922811a2..317fa20104c 100644 --- a/packages/storage/src/types/index.ts +++ b/packages/storage/src/types/index.ts @@ -32,6 +32,8 @@ export { } from './options'; export { StorageItem, + StorageItemWithKey, + StorageItemWithPath, StorageListOutput, StorageDownloadDataOutput, StorageGetUrlOutput, diff --git a/packages/storage/src/types/inputs.ts b/packages/storage/src/types/inputs.ts index f371cbbb234..403a2a14332 100644 --- a/packages/storage/src/types/inputs.ts +++ b/packages/storage/src/types/inputs.ts @@ -86,23 +86,13 @@ export interface StorageCopyInputWithKey< SourceOptions extends StorageOptions, DestinationOptions extends StorageOptions, > { - source: SourceOptions & { - path?: never; - }; - destination: DestinationOptions & { - path?: never; - }; + source: SourceOptions; + destination: DestinationOptions; } export interface StorageCopyInputWithPath { - source: StorageOperationInputWithPath & { - /** @deprecated Use path instead. */ - key?: never; - }; - destination: StorageOperationInputWithPath & { - /** @deprecated Use path instead. */ - key?: never; - }; + source: StorageOperationInputWithPath; + destination: StorageOperationInputWithPath; } /** diff --git a/packages/storage/src/types/outputs.ts b/packages/storage/src/types/outputs.ts index aa947602ca5..e38482729b8 100644 --- a/packages/storage/src/types/outputs.ts +++ b/packages/storage/src/types/outputs.ts @@ -27,15 +27,27 @@ export interface StorageItemBase { metadata?: Record; } -/** - * A storage item can be identified either by a key or a path. - */ -export type StorageItem = StorageItemBase & { - /** @deprecated This may be removed in next major version */ +/** @deprecated Use {@link StorageItemWithPath} instead. */ +export type StorageItemWithKey = StorageItemBase & { + /** + * @deprecated This may be removed in next major version. + * Key of the object. + */ key: string; +}; + +export type StorageItemWithPath = StorageItemBase & { + /** + * Path of the object. + */ path: string; }; +/** + * A storage item can be identified either by a key or a path. + */ +export type StorageItem = StorageItemWithKey | StorageItemWithPath; + export type StorageDownloadDataOutput = Item & { body: ResponseBodyMixin; };