Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 16 additions & 2 deletions src/internal/to-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,19 @@ export async function toFile(
if (value instanceof File) {
return value;
}
return makeFile([await value.arrayBuffer()], value.name);
return makeFile([await value.arrayBuffer()], value.name, {
type: value.type,
lastModified: value.lastModified,
...options,
});
}

if (isResponseLike(value)) {
const blob = await value.blob();
name ||= new URL(value.url).pathname.split(/[\\/]/).pop();
if (!options?.type && blob.type) {
options = { ...options, type: blob.type };
}

return makeFile(await getBytes(blob), name, options);
}
Expand All @@ -110,7 +117,14 @@ export async function toFile(
name ||= getName(value);

if (!options?.type) {
const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type);
const type = parts.find(
(part): part is Blob =>
typeof part === 'object' &&
part !== null &&
'type' in part &&
typeof part.type === 'string' &&
part.type.length > 0,
)?.type;
if (typeof type === 'string') {
options = { ...options, type };
}
Expand Down
30 changes: 30 additions & 0 deletions tests/uploads.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ describe('toFile', () => {
expect(file.name).toEqual('audio.mp3');
});

it('preserves the MIME type from a Response', async () => {
const response = mockResponse({
url: 'https://example.com/my/audio.mp3',
content: new Blob(['foo'], { type: 'audio/mpeg' }),
});
const file = await toFile(response);
expect(file.type).toEqual('audio/mpeg');
});

it('extracts a file name from a File', async () => {
const input = new File(['foo'], 'input.jsonl');
const file = await toFile(input);
Expand All @@ -55,6 +64,27 @@ describe('toFile', () => {
expect(file.name).toEqual('uploads.test.ts');
});

it('preserves metadata from a File-like object', async () => {
const input = {
name: 'input.jsonl',
type: 'application/jsonl',
size: 3,
lastModified: 123,
text: async () => 'foo',
slice: () => new Blob(['foo'], { type: 'application/jsonl' }),
arrayBuffer: async () => new Blob(['foo']).arrayBuffer(),
};
const file = await toFile(input);
expect(file.name).toEqual('input.jsonl');
expect(file.type).toEqual('application/jsonl');
expect(file.lastModified).toEqual(123);
});

it('infers the MIME type from Blob parts', async () => {
const file = await toFile(new Blob(['foo'], { type: 'text/plain' }), 'input.txt');
expect(file.type).toEqual('text/plain');
});

it('does not copy File objects', async () => {
const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' });
const file = await toFile(input);
Expand Down