mime-explorer 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +223 -56
- package/dist/db.d.ts +3 -0
- package/dist/db.mjs +2 -0
- package/dist/db.mjs.map +1 -0
- package/dist/errors/extension-conflict-error.d.ts +8 -0
- package/dist/errors/index.d.ts +7 -0
- package/dist/errors/invalid-extension-error.d.ts +7 -0
- package/dist/errors/invalid-type-error.d.ts +6 -0
- package/dist/errors/metadata-conflict-error.d.ts +6 -0
- package/dist/errors/mime-explorer-error.d.ts +4 -0
- package/dist/errors/missing-extensions-error.d.ts +6 -0
- package/dist/errors/registration-error.d.ts +6 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/module.d.ts +16 -38
- package/dist/type.d.ts +1 -20
- package/dist/types.d.ts +69 -0
- package/package.json +33 -47
- package/dist/index.cjs +0 -2
- package/dist/index.cjs.map +0 -1
package/README.MD
CHANGED
|
@@ -1,88 +1,255 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/tada5hi/mime-explorer/8b0d412c03e1ad24e0ca74b553d41bf2e16a2b6c/.github/assets/logo.svg" alt="mime-explorer: a cloud of file extensions" width="128" height="128" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
[](https://github.com/tada5hi/mime-explorer/actions/workflows/main.yml)
|
|
5
|
-
[](https://codecov.io/gh/tada5hi/mime-explorer)
|
|
6
|
-
[](https://snyk.io/test/github/Tada5hi/mime-explorer?targetFile=package.json)
|
|
7
|
-
[](https://github.com/semantic-release/semantic-release)
|
|
5
|
+
<h1 align="center">mime-explorer</h1>
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<b>Look up MIME types, file extensions and metadata, and register your own mappings.</b><br>
|
|
9
|
+
Use one API for file paths, Content-Type values and custom formats.<br>
|
|
10
|
+
The MIME database is bundled, as ESM with TypeScript declarations.
|
|
11
|
+
</p>
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://www.npmjs.com/package/mime-explorer"><img src="https://img.shields.io/npm/v/mime-explorer" alt="mime-explorer version on npm" /></a>
|
|
15
|
+
<a href="https://github.com/tada5hi/mime-explorer/actions/workflows/main.yml"><img src="https://github.com/tada5hi/mime-explorer/actions/workflows/main.yml/badge.svg" alt="Build and test workflow status" /></a>
|
|
16
|
+
<a href="https://codecov.io/gh/tada5hi/mime-explorer"><img src="https://codecov.io/gh/tada5hi/mime-explorer/branch/master/graph/badge.svg" alt="Test coverage on Codecov" /></a>
|
|
17
|
+
</p>
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
<p align="center">
|
|
20
|
+
<a href="#installation"><b>Installation</b></a> ·
|
|
21
|
+
<a href="#usage">Usage</a> ·
|
|
22
|
+
<a href="#custom-mappings">Custom mappings</a> ·
|
|
23
|
+
<a href="#api">API</a> ·
|
|
24
|
+
<a href="#notes">Notes</a>
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Why mime-explorer?
|
|
30
|
+
|
|
31
|
+
You need a MIME type for a filename, a default extension for a Content-Type header, or the metadata behind either. A raw MIME database supplies the records; you still need lookup rules and an extension index.
|
|
32
|
+
|
|
33
|
+
| Concern | Without it | With mime-explorer |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| File paths and extensions | Strip path segments and normalize extension case yourself | `getType()` accepts paths, extensions and dotted extensions |
|
|
36
|
+
| Content-Type parameters | Remove parameters before searching the database | `getMeta()` and `getExtension()` accept values such as `text/html; charset=utf8` |
|
|
37
|
+
| Metadata | Maintain separate access to the underlying record | `getMeta()` returns extensions, source, charset and compressibility when present |
|
|
38
|
+
| Custom formats | Keep your own mappings alongside the database | `register()` and `unregister()` modify an instance |
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { MimeExplorer } from 'mime-explorer';
|
|
42
|
+
import db from 'mime-explorer/db';
|
|
43
|
+
|
|
44
|
+
const mime = new MimeExplorer(db);
|
|
45
|
+
|
|
46
|
+
mime.getType('/uploads/report.JSON'); // 'application/json'
|
|
47
|
+
mime.getExtension('text/html; charset=utf8'); // 'html'
|
|
48
|
+
mime.getMeta('application/json')?.charset; // 'UTF-8'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
<p align="center">
|
|
52
|
+
<img src="https://raw.githubusercontent.com/tada5hi/mime-explorer/master/.github/assets/lookup.svg" alt="getType uses the extension index. getMeta and getExtension use the MIME metadata index. Each MimeExplorer instance has its own indexes." width="960" />
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
<details>
|
|
56
|
+
<summary>How the lookup indexes work</summary>
|
|
57
|
+
|
|
58
|
+
Each `MimeExplorer` instance owns two indexes, initialized from the definitions passed to its constructor. The extension index maps a file extension to its MIME type; `getType()` searches it using the final path segment and suffix. The MIME metadata index maps a MIME type to its record; `getMeta()` returns that record, and `getExtension()` returns its first extension. Unknown lookups return `undefined`.
|
|
59
|
+
|
|
60
|
+
</details>
|
|
16
61
|
|
|
17
62
|
## Installation
|
|
18
63
|
|
|
19
64
|
```bash
|
|
20
|
-
npm install mime-explorer
|
|
65
|
+
npm install mime-explorer
|
|
21
66
|
```
|
|
22
67
|
|
|
68
|
+
The package is ESM-only and ships TypeScript declarations. CommonJS code can `require()` it on Node.js 20.19, 22.12 or newer; older runtimes need `import()`. `require()` callers load a separate CommonJS copy of `@ebec/core`, so catch failures with `instanceof MimeExplorerError` rather than `BaseError`. CommonJS TypeScript needs `"module": "NodeNext"`, because `Node16` rejects ESM-only imports from `.cts` files. Its one runtime dependency is [`@ebec/core`](https://github.com/tada5hi/ebec), the base of its error classes; it has no peer dependencies. It does not declare a Node.js runtime minimum. Contributor tools require Node 24.11 or newer within the 24.x line.
|
|
69
|
+
|
|
23
70
|
## Usage
|
|
24
71
|
|
|
25
|
-
|
|
72
|
+
Import the registry and database separately:
|
|
26
73
|
|
|
27
74
|
```typescript
|
|
28
|
-
import {
|
|
75
|
+
import { MimeExplorer } from 'mime-explorer';
|
|
76
|
+
import db from 'mime-explorer/db';
|
|
77
|
+
|
|
78
|
+
const mime = new MimeExplorer(db);
|
|
79
|
+
```
|
|
29
80
|
|
|
30
|
-
|
|
31
|
-
// => 'text/plain'
|
|
81
|
+
For CommonJS:
|
|
32
82
|
|
|
33
|
-
|
|
34
|
-
|
|
83
|
+
```javascript
|
|
84
|
+
const { MimeExplorer } = require('mime-explorer');
|
|
85
|
+
const { default: db } = require('mime-explorer/db');
|
|
86
|
+
|
|
87
|
+
const mime = new MimeExplorer(db);
|
|
88
|
+
mime.getType('/uploads/report.JSON'); // 'application/json'
|
|
35
89
|
```
|
|
36
90
|
|
|
37
|
-
|
|
91
|
+
`new MimeExplorer()` creates an empty registry for custom mappings. Importing `mime-explorer` alone does not load the database or create a shared registry. The `mime-explorer/db` entrypoint provides the database as a typed default export, without JSON import attributes.
|
|
92
|
+
|
|
93
|
+
### Find a MIME type
|
|
38
94
|
|
|
39
95
|
```typescript
|
|
40
|
-
|
|
96
|
+
mime.getType('txt'); // 'text/plain'
|
|
97
|
+
mime.getType('json'); // 'application/json'
|
|
98
|
+
mime.getType('.JSON'); // 'application/json'
|
|
99
|
+
mime.getType('/uploads/report.json'); // 'application/json'
|
|
100
|
+
mime.getType('C:\\uploads\\report.json'); // 'application/json'
|
|
101
|
+
mime.getType('unknown-extension'); // undefined
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`getType()` uses the last path segment and its final extension, case-insensitively. Both `/` and `\` are path separators. A path whose final segment has no extension returns `undefined`; a bare extension such as `txt` is accepted. A dotfile in a path, such as `/uploads/.json`, is not treated as having an extension.
|
|
41
105
|
|
|
42
|
-
|
|
43
|
-
// => 'txt'
|
|
106
|
+
### Find the default extension
|
|
44
107
|
|
|
45
|
-
|
|
46
|
-
//
|
|
108
|
+
```typescript
|
|
109
|
+
mime.getExtension('text/plain'); // 'txt'
|
|
110
|
+
mime.getExtension('application/json'); // 'json'
|
|
111
|
+
mime.getExtension('TEXT/HTML; charset=utf8'); // 'html'
|
|
112
|
+
mime.getExtension('application/x-unknown-demo'); // undefined
|
|
47
113
|
```
|
|
48
114
|
|
|
49
|
-
|
|
115
|
+
`getExtension()` returns the first extension in the MIME record, without a leading dot. It ignores leading whitespace and MIME parameters, and compares the type case-insensitively.
|
|
116
|
+
|
|
117
|
+
### Read metadata
|
|
50
118
|
|
|
51
119
|
```typescript
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
120
|
+
mime.getMeta('application/json');
|
|
121
|
+
// {
|
|
122
|
+
// source: 'iana',
|
|
123
|
+
// charset: 'UTF-8',
|
|
124
|
+
// compressible: true,
|
|
125
|
+
// extensions: ['json', 'map']
|
|
126
|
+
// }
|
|
127
|
+
|
|
128
|
+
mime.getMeta('application/json; charset=ISO-8859-1')?.charset; // 'UTF-8'
|
|
129
|
+
mime.getMeta('application/x-unknown-demo'); // undefined
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`getMeta()` applies the same MIME normalization as `getExtension()`. Returned metadata comes from the registry; a header's `charset` parameter does not override it. A type with metadata but no extensions, such as `application/vnd.api+json`, has a record with empty `extensions`; `getExtension()` returns `undefined` for it and `getType()` never resolves to it.
|
|
133
|
+
|
|
134
|
+
## Custom mappings
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { MimeExplorer } from 'mime-explorer';
|
|
138
|
+
|
|
139
|
+
const mime = new MimeExplorer();
|
|
140
|
+
|
|
141
|
+
mime.register({
|
|
142
|
+
'application/x-project-note': ['pnote', 'pnt'],
|
|
143
|
+
'text/x-project-log': {
|
|
144
|
+
extensions: ['plog'],
|
|
145
|
+
charset: 'UTF-8',
|
|
146
|
+
compressible: true,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
mime.getType('meeting.PNOTE'); // 'application/x-project-note'
|
|
150
|
+
mime.getExtension('application/x-project-note'); // 'pnote'
|
|
151
|
+
mime.getMeta('text/x-project-log')?.compressible; // true
|
|
152
|
+
|
|
153
|
+
mime.unregister('application/x-project-note'); // true
|
|
154
|
+
mime.getType('pnote'); // undefined
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
- MIME keys and extensions follow the same rules as lookups, so anything registered can be found again. Keys may be Content-Type values, and extensions may have one leading dot.
|
|
158
|
+
- The first definition of a MIME record wins, and each extension has one owner: the first type that declared it. Registering a known type again can add extensions but cannot change its metadata.
|
|
159
|
+
- To replace a MIME record, call `unregister()` before registering it again. Removal does not restore an earlier owner of its extensions.
|
|
160
|
+
- `unregister()` removes the record and every extension it owns, and returns whether a record was removed.
|
|
161
|
+
- Each instance owns its registry. Definitions are not shared with other instances or saved to disk.
|
|
162
|
+
|
|
163
|
+
### Handling failures
|
|
164
|
+
|
|
165
|
+
By default `register()` applies everything it can and returns `true` when all of it was applied, `false` otherwise.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const mime = new MimeExplorer(db);
|
|
169
|
+
|
|
170
|
+
mime.register({ 'application/x-mine': ['json', 'mine'] }); // false: 'json' is owned by 'application/json'
|
|
171
|
+
mime.getType('mine'); // 'application/x-mine'
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
With `throwOnFailure`, it applies nothing and throws a `RegistrationError`, whose `errors` property lists every problem:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { RegistrationError } from 'mime-explorer';
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
mime.register(definitions, { throwOnFailure: true });
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error instanceof RegistrationError) {
|
|
183
|
+
console.error(error.errors);
|
|
184
|
+
}
|
|
82
185
|
}
|
|
83
|
-
*/
|
|
84
186
|
```
|
|
85
187
|
|
|
188
|
+
| Error | Raised when | Extra properties |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| `InvalidTypeError` | A key has no MIME type | `type` |
|
|
191
|
+
| `MissingExtensionsError` | A definition has no usable extensions or metadata | `type` |
|
|
192
|
+
| `InvalidExtensionError` | An extension is not a string, is empty, starts with `*`, or contains whitespace, `.`, `/` or `\` after its leading dot | `type`, `extension` |
|
|
193
|
+
| `ExtensionConflictError` | An extension belongs to another MIME type. Without `throwOnFailure` the record still lists it and it can be the default extension; `getType()` keeps returning the owner | `type`, `extension`, `owner` |
|
|
194
|
+
| `MetadataConflictError` | A registered type is given different metadata | `type` |
|
|
195
|
+
|
|
196
|
+
All of them extend `MimeExplorerError`, which extends `BaseError` from `@ebec/core`. The constructor never throws, because bundled data contains entries with neither extensions nor metadata and contested extensions; it keeps the first definition and skips unusable entries.
|
|
197
|
+
|
|
198
|
+
### Metadata fields
|
|
199
|
+
|
|
200
|
+
`register()` and the constructor accept a `TypeMetaCollection`: each MIME key maps to an extension array or a `TypeMetaInput` object.
|
|
201
|
+
|
|
202
|
+
| Field | Type | Behavior |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| `extensions` | `readonly string[]` | Required by `TypeMeta`; the first extension is the default. Empty for a type known only by its metadata; a `TypeMetaInput` may omit it when it sets only `source`, `charset` or `compressible` |
|
|
205
|
+
| `source` | `string` | Optional origin, for example `iana`, `apache` or `nginx` |
|
|
206
|
+
| `charset` | `string` | Optional charset recorded for the MIME type |
|
|
207
|
+
| `compressible` | `boolean` | Optional compressibility information; absence does not mean `false` |
|
|
208
|
+
|
|
209
|
+
## API
|
|
210
|
+
|
|
211
|
+
The package root exports the classes and types below. Lookup and registration methods belong to `MimeExplorer` instances; there are no standalone function helpers. The error classes are listed under [Handling failures](#handling-failures).
|
|
212
|
+
|
|
213
|
+
| Export | Purpose |
|
|
214
|
+
|---|---|
|
|
215
|
+
| `MimeExplorer` | Registry class; `new MimeExplorer(collection?)` creates an independent registry, empty by default |
|
|
216
|
+
| `IMimeExplorer` | Interface for the registry methods |
|
|
217
|
+
| `TypeMeta` | Read-only metadata with required `extensions` and optional `source`, `charset`, `compressible` |
|
|
218
|
+
| `TypeMetaInput` | `Partial<TypeMeta>` accepted in definitions |
|
|
219
|
+
| `TypeMetaCollection` | `Readonly<Record<string, readonly string[] \| TypeMetaInput>>` accepted by the constructor and `register()` |
|
|
220
|
+
| `TypeMap` | Compatibility alias for `TypeMetaCollection` |
|
|
221
|
+
| `RegisterOptions` | Options for `register()`: `throwOnFailure` (default `false`) |
|
|
222
|
+
|
|
223
|
+
Import the bundled database as the default export from `mime-explorer/db`.
|
|
224
|
+
|
|
225
|
+
| Method | Input | Returns |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| `getType(path)` | `string`: extension, dotted extension, file name or path | MIME type, or `undefined` when unknown |
|
|
228
|
+
| `getExtension(contentType)` | `string`: MIME type or Content-Type header | Default extension without a dot, or `undefined` when the type is unknown or has no extensions |
|
|
229
|
+
| `getMeta(contentType)` | `string`: MIME type or Content-Type header | Read-only `TypeMeta`, or `undefined` when unknown |
|
|
230
|
+
| `register(definitions, options?)` | `TypeMetaCollection` and optional `RegisterOptions` | `true` when everything was applied, otherwise `false` |
|
|
231
|
+
| `unregister(contentType)` | `string`: MIME type or Content-Type header | `true` when a record was removed, otherwise `false` |
|
|
232
|
+
|
|
233
|
+
## Notes
|
|
234
|
+
|
|
235
|
+
- **Extension lookup only.** The library does not inspect file contents, check that a file exists, or validate uploads. URL query strings and fragments are not stripped; pass a pathname or extension.
|
|
236
|
+
- **No fallback type.** Unknown inputs return `undefined`. Choose an application fallback such as `application/octet-stream` if needed.
|
|
237
|
+
- **Read-only metadata.** `getMeta()` returns a frozen record. Later registrations do not change a record you already hold.
|
|
238
|
+
- **Optional bundled data.** `mime-explorer/db` contains data from [mime-db](https://github.com/jshttp/mime-db). Pass it to the constructor to initialize both indexes. There are no network requests during lookups. When several types declare an extension, the database lists the preferred type first: it ranks standard subtypes above vendor subtypes and those above experimental ones, then by source with IANA first, then `video`, `font` and `audio` above `application` above other media, then the shorter name. Database updates can change mappings and default extensions.
|
|
239
|
+
|
|
240
|
+
## Contributing
|
|
241
|
+
|
|
242
|
+
Use Node 24.11 or newer within the 24.x line and npm:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
npm ci
|
|
246
|
+
npm run build
|
|
247
|
+
npm run test:coverage
|
|
248
|
+
npm run lint
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The build regenerates `src/db.json` from `mime-db` before producing `dist/`. Change the source dependency or generator rather than editing the generated database. Tests live in `test/unit/`; open changes through the [issue tracker](https://github.com/tada5hi/mime-explorer/issues) or a pull request.
|
|
252
|
+
|
|
86
253
|
## License
|
|
87
254
|
|
|
88
255
|
Made with 💚
|
package/dist/db.d.ts
ADDED