Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Typescript typings + rework #37

Merged
merged 6 commits into from
May 8, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
12 changes: 6 additions & 6 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@ Mimos is a convenience class for retrieving mime information objects.

## Usage

### `new Mimos([options])`
### `new Mimos.Mimos([options])`

Creates a new Mimos object where:

- `[options]` - an option object the following keys
- `[override]` - an object hash that is merged into the built in mime information specified [here](https://github.com/jshttp/mime-db). Each key value pair represents a single mime object. Each override value should follow this schema:
- `key` - the key is the lower-cased correct mime-type. (Ex. "application/javascript").
- `value` - the value should an object following the specifications outlined [here](https://github.com/jshttp/mime-db#data-structure). Additional values include:
- `value` - the value should be an object following the specifications outlined [here](https://github.com/jshttp/mime-db#data-structure). Additional values include:
- `type` - specify the `type` value of result objects, defaults to `key`. See the example below for more clarification.
- `predicate` - method with signature `function(mime)` when this mime type is found in the database, this function will run. This allows you make customizations to `mime` based on developer criteria.

### `mimos.path(path)`

Returns mime object where:
Lookup file extension from path and return mime object, or `null` if not found, where:
kanongil marked this conversation as resolved.
Show resolved Hide resolved

- `path` path to file including the file extension. Uses the `extension` values of the mime objects for lookup.

```js
const mimos = new Mimos();
const mimos = new Mimos.Mimos();
const mime = mimos.path('/static/public/app.js');
// mime
/*
Expand All @@ -42,7 +42,7 @@ Returns mime object where:
- `type` the content-type to find mime information about. Uses the `type` values of the mime objects for lookup.

```js
const mimos = new Mimos();
const mimos = new Mimos.Mimos();
const mime = mimos.type('text/plain');
// mime
/*
Expand Down Expand Up @@ -90,7 +90,7 @@ const options = {
}
}

const mimos = new Mimos(options);
const mimos = new Mimos.Mimos(options);
console.dir(mimos.path('./node_modules/mimos.module'));
/*
{
Expand Down
134 changes: 134 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Declare our own interface to mime-db data

declare namespace MimeDb {

type MimeSource = 'iana' | 'apache' | 'nginx';

interface MimeEntry {

/**
* String with identifier for the source of the data.
*/
source?: MimeSource;

/**
* Array of strings with possible lowercased file extensions, without the
* dot.
*/
extensions?: ReadonlyArray<string>;

/**
* Boolean that indicates if the contents is likely to become smaller if
* gzip or similar compression is applied.
*/
compressible?: boolean;

/**
* Charset for type.
*/
charset?: string;
}
}


// Helpers

type NoInfer<T> = [T][T extends any ? 0 : never];


export class MimosEntry {

/**
* String with the content-type.
*/
type: string;

/**
* String with identifier for the source of the data.
*/
source: MimeDb.MimeSource | 'mime-db' | 'mimos';

/**
* Array of strings with possible lowercased file extensions, without the
* dot.
*/
extensions: ReadonlyArray<string>;

/**
* Boolean that indicates if the contents is likely to become smaller if
* gzip or similar compression is applied.
*/
compressible: boolean;

/**
* Optional charset for type.
*/
charset?: string;

private constructor();
}

export interface MimosDeclaration<P extends object = {}> extends MimeDb.MimeEntry {

/**
* The `type` value of result objects, defaults to `key`.
*/
type?: string;

/**
* Method with signature `function(mime)`.
*
* When this mime type is found in the database, this function will run.
* This allows you make customizations to `mime` based on developer criteria.
*/
predicate?: (mime: MimosEntry & P) => MimosEntry;
}

export interface MimosOptions<P extends object = {}> {

/**
* An object hash that is merged into the built-in mime information from
* {@link https://github.com/jshttp/mime-db}.
*
* Each key value pair represents a single mime object override.
*
* Each override entry should follow this schema:
* * The key is the lower-cased correct mime-type. (Ex. "application/javascript").
* * The value should be an object following the structure from
* {@link https://github.com/jshttp/mime-db#data-structure} with additional
* optional values:
* * type - Specify the `type` value of result objects, defaults to `key`.
* * predicate - Method that is called with mime entry on lookup, that
* must return an entry. This allows you make customizations to `mime`
* based on developer criteria.
*/
override?: {
[type: string]: MimosDeclaration<P> & P;
};
}

export class Mimos<P extends object = {}> {

/**
* Create a Mimos object for mime lookups.
*/
constructor(options?: MimosOptions<NoInfer<P>>);

/**
* Extract extension from file path and lookup mime information.
*
* @param path - Path to file
*
* @return Found mime object, or {} if no match.
*/
path(path: string): (Readonly<MimosEntry & Partial<P>>) | {};

/**
* Lookup mime information.
*
* @param type - The content-type to find mime information about.
*
* @return Mime object for provided type.
*/
type(type: string): Readonly<MimosEntry & Partial<P>>;
}
151 changes: 103 additions & 48 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,87 +6,142 @@ const Hoek = require('@hapi/hoek');
const MimeDb = require('mime-db/db.json'); // Load JSON file to prevent loading or executing code


const internals = {};
const internals = {
compressibleRx: /^text\/|\+json$|\+text$|\+xml$/
};


internals.compressibleRx = /^text\/|\+json$|\+text$|\+xml$/;
exports.MimosEntry = class {

constructor(type, mime) {

internals.compile = function (override) {
this.type = type;
this.source = 'mime-db';
this.extensions = [];
this.compressible = undefined;

const db = Hoek.clone(MimeDb);
Hoek.merge(db, override, { nullOverride: true, mergeArrays: false });
Object.assign(this, mime);

const result = {
byType: db,
byExtension: {}
};
if (this.compressible === undefined) {
this.compressible = internals.compressibleRx.test(type);
}
}
};

const keys = Object.keys(result.byType);
for (let i = 0; i < keys.length; ++i) {
const type = keys[i];
const mime = result.byType[type];
mime.type = mime.type || type;
mime.source = mime.source || 'mime-db';
mime.extensions = mime.extensions || [];
mime.compressible = (mime.compressible !== undefined ? mime.compressible : internals.compressibleRx.test(type));

Hoek.assert(!mime.predicate || typeof mime.predicate === 'function', 'predicate option must be a function');
internals.insertEntry = function (type, entry, db) {

for (let j = 0; j < mime.extensions.length; ++j) {
const ext = mime.extensions[j];
result.byExtension[ext] = mime;
db.byType.set(type, entry);
for (const ext of entry.extensions) {
db.byExtension.set(ext, entry);
if (ext.length > db.maxExtLength) {
db.maxExtLength = ext.length;
}
}
};

return result;

internals.compile = function (mimedb) {

const db = {
byType: new Map(),
byExtension: new Map(),
maxExtLength: 0
};

for (const type in mimedb) {
const entry = new exports.MimosEntry(type, mimedb[type]);
internals.insertEntry(type, entry, db);
}

return db;
};


internals.getTypePart = function (fulltype) {

const splitAt = fulltype.indexOf(';');
return splitAt === -1 ? fulltype : fulltype.slice(0, splitAt);
};


module.exports = class Mimos {
constructor(options) {
internals.applyPredicate = function (mime) {

options = options || {};
const result = options.override ? internals.compile(options.override) : internals.base;
this._byType = result.byType;
this._byExtension = result.byExtension;
if (mime.predicate) {
return mime.predicate(Hoek.clone(mime));
}

path(path) {
return mime;
};

const extension = Path.extname(path).slice(1).toLowerCase();
const mime = this._byExtension[extension] || {};

if (mime.predicate) {
return mime.predicate(Hoek.clone(mime));
exports.Mimos = class Mimos {

#db = internals.base;

constructor(options = {}) {

if (options.override) {
Hoek.assert(typeof options.override === 'object', 'overrides option must be an object');
Nargonath marked this conversation as resolved.
Show resolved Hide resolved

// Shallow clone db

this.#db = {
...this.#db,
byType: new Map(this.#db.byType),
byExtension: new Map(this.#db.byExtension)
};

// Apply overrides

for (const type in options.override) {
const override = options.override[type];
Hoek.assert(!override.predicate || typeof override.predicate === 'function', 'predicate option must be a function');

const from = this.#db.byType.get(type);
const baseEntry = from ? Hoek.applyToDefaults(from, override) : override;

const entry = new exports.MimosEntry(type, baseEntry);
internals.insertEntry(type, entry, this.#db);
}
}
}

path(path) {

return mime;
const extension = Path.extname(path).slice(1).toLowerCase();
const mime = this.#db.byExtension.get(extension) || {};

return internals.applyPredicate(mime);
}

type(type) {

type = type.split(';', 1)[0].trim().toLowerCase();
let mime = this._byType[type];
type = internals.getTypePart(type);

let mime = this.#db.byType.get(type);
if (!mime) {
mime = {
type,
source: 'mimos',
extensions: [],
compressible: internals.compressibleRx.test(type)
};
// Retry with more expensive adaptations

this._byType[type] = mime;
return mime;
type = type.trim().toLowerCase();
mime = this.#db.byType.get(type);
}

if (mime.predicate) {
return mime.predicate(Hoek.clone(mime));
if (!mime) {
mime = new exports.MimosEntry(type, {
source: 'mimos'
});

// Cache the entry

internals.insertEntry(type, mime, this.#db);

return mime;
}

return mime;
return internals.applyPredicate(mime);
}
};


internals.base = internals.compile(); // Prevents an expensive copy on each constructor when no customization is needed
internals.base = internals.compile(MimeDb);
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"version": "5.0.0",
"repository": "git://github.com/hapijs/mimos",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib"
],
Expand All @@ -18,10 +19,12 @@
},
"devDependencies": {
"@hapi/code": "8.x.x",
"@hapi/lab": "24.x.x"
"@hapi/lab": "24.x.x",
devinivy marked this conversation as resolved.
Show resolved Hide resolved
"typescript": "^4.0.7"
},
"scripts": {
"test": "lab -m 5000 -t 100 -L -a @hapi/code"
"test": "lab -m 5000 -t 100 -L -a @hapi/code -Y",
"test-cov-html": "lab -a @hapi/code -r html -o coverage.html -L"
},
"license": "BSD-3-Clause"
}
Loading