Skip to content

Commit

Permalink
Move Node.js dependencies from core.js to index.js
Browse files Browse the repository at this point in the history
Introduce class `NodeFileTypeParser`, overriding `fromStream()`, which also allow a Node.js Readable stream.
  • Loading branch information
Borewit committed Jul 6, 2024
1 parent 1fce3c4 commit 9d0cfaf
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 29 deletions.
11 changes: 5 additions & 6 deletions core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Typings for primary entry point, Node.js specific typings can be found in index.d.ts
*/

import type {Readable as NodeReadableStream} from 'node:stream';
import type {ReadableStream as WebReadableStream} from 'node:stream/web';
import type {ITokenizer} from 'strtok3';

Expand Down Expand Up @@ -323,7 +322,7 @@ export type FileTypeResult = {
readonly mime: MimeType;
};

export type ReadableStreamWithFileType = NodeReadableStream & {
export type ReadableStreamWithFileType = WebReadableStream & {
readonly fileType?: FileTypeResult;
};

Expand All @@ -347,7 +346,7 @@ The file type is detected by checking the [magic number](https://en.wikipedia.or
@param stream - A Node.js Readable stream or Web API Readable Stream representing file data. The Web Readable stream **must be a byte stream**.
@returns The detected file type, or `undefined` when there is no match.
*/
export function fileTypeFromStream(stream: NodeReadableStream | WebReadableStream): Promise<FileTypeResult | undefined>;
export function fileTypeFromStream(stream: WebReadableStream): Promise<FileTypeResult | undefined>;

/**
Detect the file type from an [`ITokenizer`](https://github.com/Borewit/strtok3#tokenizer) source.
Expand Down Expand Up @@ -425,7 +424,7 @@ if (stream2.fileType?.mime === 'image/jpeg') {
}
```
*/
export function fileTypeStream(readableStream: NodeReadableStream, options?: StreamOptions): Promise<ReadableStreamWithFileType>;
export function fileTypeStream(readableStream: WebReadableStream, options?: StreamOptions): Promise<ReadableStreamWithFileType>;

/**
Detect the file type of a [`Blob`](https://nodejs.org/api/buffer.html#class-blob) or [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File).
Expand Down Expand Up @@ -516,7 +515,7 @@ export declare class FileTypeParser {
/**
Works the same way as {@link fileTypeFromStream}, additionally taking into account custom detectors (if any were provided to the constructor).
*/
fromStream(stream: NodeReadableStream | WebReadableStream): Promise<FileTypeResult | undefined>;
fromStream(stream: WebReadableStream): Promise<FileTypeResult | undefined>;

/**
Works the same way as {@link fileTypeFromTokenizer}, additionally taking into account custom detectors (if any were provided to the constructor).
Expand All @@ -531,5 +530,5 @@ export declare class FileTypeParser {
/**
Works the same way as {@link fileTypeStream}, additionally taking into account custom detectors (if any were provided to the constructor).
*/
toDetectionStream(readableStream: NodeReadableStream, options?: StreamOptions): Promise<FileTypeResult | undefined>;
toDetectionStream(readableStream: WebReadableStream, options?: StreamOptions): Promise<FileTypeResult | undefined>;
}
3 changes: 1 addition & 2 deletions core.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Primary entry point, Node.js specific entry point is index.js
*/

import {ReadableStream as WebReadableStream} from 'node:stream/web';
import * as Token from 'token-types';
import * as strtok3 from 'strtok3/core';
import {includes, indexOf, getUintBE} from 'uint8array-extras';
Expand Down Expand Up @@ -97,7 +96,7 @@ export class FileTypeParser {
}

async fromStream(stream) {
const tokenizer = await (stream instanceof WebReadableStream ? strtok3.fromWebStream(stream) : strtok3.fromStream(stream));
const tokenizer = await strtok3.fromWebStream(stream);
try {
return await this.fromTokenizer(tokenizer);
} finally {
Expand Down
13 changes: 13 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@
* Typings for Node.js specific entry point
*/

import type {Readable as NodeReadableStream} from 'node:stream';
import type {ReadableStream as WebReadableStream} from 'node:stream/web';
import type {FileTypeResult} from './core.js';
import {FileTypeParser} from './core.js';

export declare class NodeFileTypeParser extends FileTypeParser {
/**
*
* @param stream Node.js Stream readable or Web API StreamReadable
*/
fromStream(stream: WebReadableStream): Promise<FileTypeResult | undefined>;
}

/**
* Detect the file type of a file path.
Expand All @@ -14,4 +25,6 @@ import type {FileTypeResult} from './core.js';
*/
export function fileTypeFromFile(path: string): Promise<FileTypeResult | undefined>;

export function fileTypeFromStream(stream: WebReadableStream | NodeReadableStream): Promise<FileTypeResult | undefined>;

export * from './core.js';
18 changes: 17 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,21 @@
* Node.js specific entry point
*/

import {ReadableStream as WebReadableStream} from 'node:stream/web';
import * as strtok3 from 'strtok3';
import {FileTypeParser} from './core.js';

export class NodeFileTypeParser extends FileTypeParser {
async fromStream(stream) {
const tokenizer = await (stream instanceof WebReadableStream ? strtok3.fromWebStream(stream) : strtok3.fromStream(stream));
try {
return super.fromTokenizer(tokenizer);
} finally {
await tokenizer.close();
}
}
}

export async function fileTypeFromFile(path, fileTypeOptions) {
const tokenizer = await strtok3.fromFile(path);
try {
Expand All @@ -15,4 +27,8 @@ export async function fileTypeFromFile(path, fileTypeOptions) {
}
}

export * from './core.js';
export async function fileTypeFromStream(stream, fileTypeOptions) {
return (new NodeFileTypeParser(fileTypeOptions)).fromStream(stream);
}

export {fileTypeFromBuffer, fileTypeFromBlob, fileTypeStream, FileTypeParser, supportedMimeTypes, supportedExtensions} from './core.js';
6 changes: 3 additions & 3 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ expectType<ReadonlySet<FileExtension>>(supportedExtensions);

expectType<ReadonlySet<MimeType>>(supportedMimeTypes);

const readableStream = createReadStream('file.png');
const streamWithFileType = fileTypeStream(readableStream);
const emptyBlob = new Blob([]);
const streamWithFileType = fileTypeStream(emptyBlob.stream());
expectType<Promise<ReadableStreamWithFileType>>(streamWithFileType);
(async () => {
const {fileType} = await streamWithFileType;
expectType<FileTypeResult | undefined>(fileType);
})();

// Browser
expectType<Promise<FileTypeResultBrowser | undefined>>(fileTypeFromBlob(new Blob()));
expectType<Promise<FileTypeResultBrowser | undefined>>(fileTypeFromBlob(new Blob([])));
34 changes: 17 additions & 17 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import * as strtok3 from 'strtok3/core';
import {areUint8ArraysEqual} from 'uint8array-extras';
import {
fileTypeFromBuffer,
fileTypeFromStream,
fileTypeFromStream as fileTypeNodeFromStream,
fileTypeFromFile,
fileTypeFromBlob,
FileTypeParser,
fileTypeStream,
supportedExtensions,
supportedMimeTypes,
NodeFileTypeParser,
} from './index.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
Expand Down Expand Up @@ -323,10 +323,10 @@ async function testFalsePositive(t, ext, name) {
t.is(await fileTypeFromBuffer(chunk.buffer), undefined);
}

async function testFileFromStream(t, ext, name) {
async function testFileNodeFromStream(t, ext, name) {
const filename = `${(name ?? 'fixture')}.${ext}`;
const file = path.join(__dirname, 'fixture', filename);
const fileType = await fileTypeFromStream(fs.createReadStream(file));
const fileType = await fileTypeNodeFromStream(fs.createReadStream(file));

t.truthy(fileType, `identify ${filename}`);
t.is(fileType.ext, ext, 'fileType.ext');
Expand Down Expand Up @@ -383,7 +383,7 @@ for (const type of types) {
_test(`${name}.${type} ${i++} .fileTypeFromBlob() method - same fileType`, testFromBlob, type, name);
}

_test(`${name}.${type} ${i++} .fileTypeFromStream() method - same fileType`, testFileFromStream, type, name);
_test(`${name}.${type} ${i++} .fileTypeFromStream() Node.js method - same fileType`, testFileNodeFromStream, type, name);
test(`${name}.${type} ${i++} .fileTypeStream() - identical streams`, testStream, type, name);
}
} else {
Expand All @@ -392,7 +392,7 @@ for (const type of types) {

_test(`${type} ${i++} .fileTypeFromFile()`, testFromFile, type);
_test(`${type} ${i++} .fileTypeFromBuffer()`, testFromBuffer, type);
_test(`${type} ${i++} .fileTypeFromStream()`, testFileFromStream, type);
_test(`${type} ${i++} .fileTypeFromStream() Node.js`, testFileNodeFromStream, type);
test(`${type} ${i++} .fileTypeStream() - identical streams`, testStream, type);
}

Expand Down Expand Up @@ -647,7 +647,7 @@ test('odd file sizes', async t => {
for (const size of oddFileSizes) {
const buffer = new Uint8Array(size);
const stream = new BufferedStream(buffer);
await t.notThrowsAsync(fileTypeFromStream(stream), `fromStream: File size: ${size} bytes`);
await t.notThrowsAsync(fileTypeNodeFromStream(stream), `fromStream: File size: ${size} bytes`);
}
});

Expand Down Expand Up @@ -713,7 +713,7 @@ if (nodeMajorVersion >= nodeVersionSupportingByeBlobStream) {
const blob = new Blob([header]);

const customDetectors = [unicornDetector];
const parser = new FileTypeParser({customDetectors});
const parser = new NodeFileTypeParser({customDetectors});

const result = await parser.fromBlob(blob);
t.deepEqual(result, {ext: 'unicorn', mime: 'application/unicorn'});
Expand All @@ -725,7 +725,7 @@ if (nodeMajorVersion >= nodeVersionSupportingByeBlobStream) {
const blob = new Blob([chunk]);

const customDetectors = [unicornDetector];
const parser = new FileTypeParser({customDetectors});
const parser = new NodeFileTypeParser({customDetectors});

const result = await parser.fromBlob(blob);
t.deepEqual(result, {ext: 'png', mime: 'image/png'});
Expand All @@ -737,7 +737,7 @@ if (nodeMajorVersion >= nodeVersionSupportingByeBlobStream) {
const blob = new Blob([chunk]);

const customDetectors = [mockPngDetector];
const parser = new FileTypeParser({customDetectors});
const parser = new NodeFileTypeParser({customDetectors});

const result = await parser.fromBlob(blob);
t.deepEqual(result, {ext: 'mockPng', mime: 'image/mockPng'});
Expand All @@ -749,7 +749,7 @@ test('fileTypeFromBuffer should detect custom file type "unicorn" using custom d
const uint8ArrayContent = new TextEncoder().encode(header);

const customDetectors = [unicornDetector];
const parser = new FileTypeParser({customDetectors});
const parser = new NodeFileTypeParser({customDetectors});

const result = await parser.fromBuffer(uint8ArrayContent);
t.deepEqual(result, {ext: 'unicorn', mime: 'application/unicorn'});
Expand All @@ -760,7 +760,7 @@ test('fileTypeFromBuffer should keep detecting default file types when no custom
const uint8ArrayContent = fs.readFileSync(file);

const customDetectors = [unicornDetector];
const parser = new FileTypeParser({customDetectors});
const parser = new NodeFileTypeParser({customDetectors});

const result = await parser.fromBuffer(uint8ArrayContent);
t.deepEqual(result, {ext: 'png', mime: 'image/png'});
Expand All @@ -771,7 +771,7 @@ test('fileTypeFromBuffer should allow overriding default file type detectors', a
const uint8ArrayContent = fs.readFileSync(file);

const customDetectors = [mockPngDetector];
const parser = new FileTypeParser({customDetectors});
const parser = new NodeFileTypeParser({customDetectors});

const result = await parser.fromBuffer(uint8ArrayContent);
t.deepEqual(result, {ext: 'mockPng', mime: 'image/mockPng'});
Expand All @@ -786,7 +786,7 @@ test('fileTypeFromStream should detect custom file type "unicorn" using custom d
const readableStream = new CustomReadableStream();

const customDetectors = [unicornDetector];
const parser = new FileTypeParser({customDetectors});
const parser = new NodeFileTypeParser({customDetectors});

const result = await parser.fromStream(readableStream);
t.deepEqual(result, {ext: 'unicorn', mime: 'application/unicorn'});
Expand All @@ -797,7 +797,7 @@ test('fileTypeFromStream should keep detecting default file types when no custom
const readableStream = fs.createReadStream(file);

const customDetectors = [unicornDetector];
const parser = new FileTypeParser({customDetectors});
const parser = new NodeFileTypeParser({customDetectors});

const result = await parser.fromStream(readableStream);
t.deepEqual(result, {ext: 'png', mime: 'image/png'});
Expand All @@ -808,7 +808,7 @@ test('fileTypeFromStream should allow overriding default file type detectors', a
const readableStream = fs.createReadStream(file);

const customDetectors = [mockPngDetector];
const parser = new FileTypeParser({customDetectors});
const parser = new NodeFileTypeParser({customDetectors});

const result = await parser.fromStream(readableStream);
t.deepEqual(result, {ext: 'mockPng', mime: 'image/mockPng'});
Expand Down Expand Up @@ -847,7 +847,7 @@ test('fileTypeFromTokenizer should return undefined when a custom detector chang

// Include the unicormDetector here to verify it's not used after the tokenizer.position changed
const customDetectors = [tokenizerPositionChanger, unicornDetector];
const parser = new FileTypeParser({customDetectors});
const parser = new NodeFileTypeParser({customDetectors});

const result = await parser.fromTokenizer(strtok3.fromBuffer(uint8ArrayContent));
t.is(result, undefined);
Expand Down

0 comments on commit 9d0cfaf

Please sign in to comment.