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 CHANGED
@@ -1,88 +1,255 @@
1
- # mime-explorer ☂
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
- [![npm version](https://badge.fury.io/js/mime-explorer.svg)](https://badge.fury.io/js/mime-explorer)
4
- [![main](https://github.com/tada5hi/mime-explorer/actions/workflows/main.yml/badge.svg)](https://github.com/tada5hi/mime-explorer/actions/workflows/main.yml)
5
- [![codecov](https://codecov.io/gh/tada5hi/mime-explorer/branch/master/graph/badge.svg?token=80LA00PIGM)](https://codecov.io/gh/tada5hi/mime-explorer)
6
- [![Known Vulnerabilities](https://snyk.io/test/github/Tada5hi/mime-explorer/badge.svg?targetFile=package.json)](https://snyk.io/test/github/Tada5hi/mime-explorer?targetFile=package.json)
7
- [![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release)
5
+ <h1 align="center">mime-explorer</h1>
8
6
 
9
- This is a library for mime types. It provides an ESM (136kb) and CJS (136kb) build.
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
- **Table of Contents**
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
- - [Installation](#installation)
14
- - [Usage](#usage)
15
- - [License](#license)
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 --save
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
- To get the mime type for a given path or extension:
72
+ Import the registry and database separately:
26
73
 
27
74
  ```typescript
28
- import { getType } from 'mime-explorer';
75
+ import { MimeExplorer } from 'mime-explorer';
76
+ import db from 'mime-explorer/db';
77
+
78
+ const mime = new MimeExplorer(db);
79
+ ```
29
80
 
30
- getType('txt');
31
- // => 'text/plain'
81
+ For CommonJS:
32
82
 
33
- getType('json');
34
- // => 'application/json'
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
- To get the file extension for a given mime type:
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
- import { getExtension } from 'mime-explorer';
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
- getExtension('text/plain');
43
- // => 'txt'
106
+ ### Find the default extension
44
107
 
45
- getExtension('application/json');
46
- // => 'json'
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
- To get meta information for a mime type:
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
- import { get } from 'mime-explorer';
53
-
54
- get('text/plain');
55
- /*
56
- {
57
- "source": "iana",
58
- "compressible": true,
59
- "extensions": [
60
- "txt",
61
- "text",
62
- "conf",
63
- "def",
64
- "list",
65
- "log",
66
- "in",
67
- "ini"
68
- ]
69
- }
70
- */
71
-
72
- get('application/json');
73
- /*
74
- {
75
- "source": "iana",
76
- "charset": "UTF-8",
77
- "compressible": true,
78
- "extensions": [
79
- "json",
80
- "map"
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
@@ -0,0 +1,3 @@
1
+ import type { TypeMetaCollection } from './types.ts';
2
+ declare const db: TypeMetaCollection;
3
+ export default db;