Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7ef4156
feat(storage): add server uploadData api
osama-rizk Apr 29, 2026
ef355fb
add a changeset
osama-rizk Apr 29, 2026
9d87514
update upload data api types
osama-rizk Apr 29, 2026
67ebab2
docs(storage): fix @throws JSDoc syntax in uploadData APIs
osama-rizk Apr 30, 2026
fa98843
Merge branch 'main' into feat/storage/upload-data-api
osama-rizk Apr 30, 2026
12af5cf
Merge branch 'main' into feat/storage/upload-data-api
osama-rizk Apr 30, 2026
a71bed7
Add client layer
osama-rizk Apr 29, 2026
2822a8d
export server API
osama-rizk Apr 30, 2026
3ffa268
refactor(storage): inject readFile via FoundationContext
osama-rizk Apr 30, 2026
0aa15c3
refactor(storage): inject toBase64 via FoundationContext
osama-rizk Apr 30, 2026
090abf4
test(storage): update uploadData tests for FoundationContext
osama-rizk Apr 30, 2026
97d6321
add a changeset
osama-rizk Apr 30, 2026
5d229be
Merge remote-tracking branch 'origin/main' into upload-data-api-three…
osama-rizk May 4, 2026
74ea9d8
add conditional exports
osama-rizk May 4, 2026
c256488
fix(storage): jest configs pin to node condition after browser export
osama-rizk May 4, 2026
9df46ec
fix: update change set config. (#14800)
osama-rizk May 5, 2026
19ebe2e
Merge branch 'main' into upload-data-api-three-layer-refactor
osama-rizk May 5, 2026
aa47ed6
test(storage): add unit tests for foundation, client and server utils
osama-rizk May 5, 2026
337c101
test: split aws-amplify exports into node and browser jest projects
osama-rizk May 5, 2026
d53aad9
Merge branch 'main' into upload-data-api-three-layer-refactor
osama-rizk May 5, 2026
b46de7c
revert(storage): restore pre-refactor exports["."] and jest configs
osama-rizk May 7, 2026
46eacc1
Merge branch 'main' into upload-data-api-three-layer-refactor
osama-rizk May 7, 2026
ac2ea76
fix(storage): honor byteOffset/byteLength in native and server toBase64
osama-rizk May 8, 2026
c8700ed
Merge branch 'main' into upload-data-api-three-layer-refactor
osama-rizk May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/storage-uploaddata-3-layer-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@aws-amplify/storage': minor
'aws-amplify': minor
---

refactor(storage): internal `uploadData` API refactoring for architectural consistency.
90 changes: 90 additions & 0 deletions packages/storage/__tests__/client/utils/readFile.native.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { Buffer } from 'buffer';

import { readFile } from '../../../src/client/utils/readFile.native';

jest.mock('buffer', () => ({
Buffer: {
from: jest.fn(() => new Uint8Array()),
},
}));

describe('client/utils/readFile.native', () => {
let mockFileReader: any;

beforeEach(() => {
mockFileReader = {
onload: null,
onabort: null,
onerror: null,
readAsArrayBuffer: jest.fn(),
readAsDataURL: jest.fn(),
result: null,
};

(global as any).FileReader = jest.fn(() => mockFileReader);
});

afterEach(() => {
jest.resetAllMocks();
});

it('resolves with ArrayBuffer when readAsArrayBuffer is supported', async () => {
const blob = new Blob(['content']);
const buffer = new ArrayBuffer(8);

mockFileReader.readAsArrayBuffer.mockImplementation(() => {
mockFileReader.result = buffer;
mockFileReader.onload();
});

const result = await readFile(blob);

expect(mockFileReader.readAsArrayBuffer).toHaveBeenCalledWith(blob);
expect(result).toBe(buffer);
});

it('falls back to readAsDataURL + Buffer when readAsArrayBuffer throws (RN 0.71 workaround)', async () => {
const blob = new Blob(['content']);
const base64 = 'base64data';
const dataURL = `data:application/octet-stream;base64,${base64}`;

mockFileReader.readAsArrayBuffer.mockImplementation(() => {
throw new Error('readAsArrayBuffer not supported');
});
mockFileReader.readAsDataURL.mockImplementation(() => {
mockFileReader.result = dataURL;
mockFileReader.onload();
});

await readFile(blob);

expect(mockFileReader.readAsArrayBuffer).toHaveBeenCalledWith(blob);
expect(mockFileReader.readAsDataURL).toHaveBeenCalledWith(blob);
expect(Buffer.from).toHaveBeenCalledWith(base64, 'base64');
});

it('rejects with "Read aborted" when onabort fires', async () => {
const blob = new Blob(['content']);

mockFileReader.readAsArrayBuffer.mockImplementation(() => {
mockFileReader.onabort();
});

await expect(readFile(blob)).rejects.toThrow('Read aborted');
});

it('rejects with the FileReader error when onerror fires', async () => {
const blob = new Blob(['content']);
const err = new Error('boom');

mockFileReader.readAsArrayBuffer.mockImplementation(() => {
mockFileReader.error = err;
mockFileReader.onerror();
});

await expect(readFile(blob)).rejects.toThrow(err);
});
});
76 changes: 76 additions & 0 deletions packages/storage/__tests__/client/utils/readFile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { readFile } from '../../../src/client/utils/readFile';

describe('client/utils/readFile', () => {
let mockFileReader: any;

beforeEach(() => {
mockFileReader = {
onload: null,
onabort: null,
onerror: null,
readAsArrayBuffer: jest.fn(),
result: null,
};

(global as any).FileReader = jest.fn(() => mockFileReader);
});

afterEach(() => {
jest.resetAllMocks();
});

it('resolves with the ArrayBuffer produced by FileReader', async () => {
const blob = new Blob(['content']);
const buffer = new ArrayBuffer(8);

mockFileReader.readAsArrayBuffer.mockImplementation(() => {
mockFileReader.result = buffer;
mockFileReader.onload();
});

const result = await readFile(blob);

expect(mockFileReader.readAsArrayBuffer).toHaveBeenCalledWith(blob);
expect(result).toBe(buffer);
});

it('rejects with "Read aborted" when FileReader onabort fires', async () => {
const blob = new Blob(['content']);

mockFileReader.readAsArrayBuffer.mockImplementation(() => {
mockFileReader.onabort();
});

await expect(readFile(blob)).rejects.toThrow('Read aborted');
});

it('rejects with the FileReader error when onerror fires', async () => {
const blob = new Blob(['content']);
const err = new Error('boom');

mockFileReader.readAsArrayBuffer.mockImplementation(() => {
mockFileReader.error = err;
mockFileReader.onerror();
});

await expect(readFile(blob)).rejects.toThrow(err);
});

it('handles empty blobs', async () => {
const blob = new Blob([]);
const buffer = new ArrayBuffer(0);

mockFileReader.readAsArrayBuffer.mockImplementation(() => {
mockFileReader.result = buffer;
mockFileReader.onload();
});

const result = await readFile(blob);

expect(result).toBe(buffer);
expect(result.byteLength).toBe(0);
});
});
33 changes: 33 additions & 0 deletions packages/storage/__tests__/client/utils/toBase64.native.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { toBase64 } from '../../../src/client/utils/toBase64.native';

describe('client/utils/toBase64.native (React Native)', () => {
it('encodes an ASCII string', () => {
expect(toBase64('hello')).toBe('aGVsbG8=');
});

it('encodes an empty string', () => {
expect(toBase64('')).toBe('');
});

it('encodes a multi-byte UTF-8 string correctly', () => {
expect(toBase64('héllo')).toBe('aMOpbGxv');
});

it('encodes a Uint8Array', () => {
const bytes = new Uint8Array([104, 101, 108, 108, 111]);
expect(toBase64(bytes)).toBe('aGVsbG8=');
});

it('encodes a Uint8Array view over an offset into an ArrayBuffer', () => {
const { buffer } = new Uint8Array([0, 0, 104, 101, 108, 108, 111, 0]);
const view = new Uint8Array(buffer, 2, 5); // "hello"
expect(toBase64(view)).toBe('aGVsbG8=');
});

it('encodes an empty ArrayBufferView', () => {
expect(toBase64(new Uint8Array())).toBe('');
});
});
40 changes: 40 additions & 0 deletions packages/storage/__tests__/client/utils/toBase64.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { TextEncoder as TextEncoderPolyfill } from 'node:util';

import { toBase64 } from '../../../src/client/utils/toBase64';

// jsdom does not expose `TextEncoder` globally; polyfill it from node:util so
// the browser implementation under test can resolve it.
global.TextEncoder = TextEncoderPolyfill as any;

describe('client/utils/toBase64 (browser)', () => {
it('encodes an ASCII string', () => {
expect(toBase64('hello')).toBe('aGVsbG8=');
});

it('encodes an empty string', () => {
expect(toBase64('')).toBe('');
});

it('encodes a multi-byte UTF-8 string correctly', () => {
// "héllo" — 'é' is 0xC3 0xA9 in UTF-8
expect(toBase64('héllo')).toBe('aMOpbGxv');
});

it('encodes a Uint8Array', () => {
const bytes = new Uint8Array([104, 101, 108, 108, 111]); // "hello"
expect(toBase64(bytes)).toBe('aGVsbG8=');
});

it('encodes a Uint8Array view over an offset into an ArrayBuffer', () => {
const { buffer } = new Uint8Array([0, 0, 104, 101, 108, 108, 111, 0]);
const view = new Uint8Array(buffer, 2, 5); // "hello"
expect(toBase64(view)).toBe('aGVsbG8=');
});

it('encodes an empty ArrayBufferView', () => {
expect(toBase64(new Uint8Array())).toBe('');
});
});
Loading
Loading