express-version-api 1.0.6 → 2.0.5
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/CHANGELOG.md +63 -0
- package/README.md +242 -101
- package/dist/index.cjs +708 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +444 -0
- package/dist/index.js +689 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -31
- package/dist/cjs/index.js +0 -11
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/lib.js +0 -209
- package/dist/cjs/lib.js.map +0 -1
- package/dist/esm/index.js +0 -4
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/lib.js +0 -201
- package/dist/esm/lib.js.map +0 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/lib.d.ts +0 -137
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [2.0.4] - 2026-02-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added `jsr.json` configuration and JSR package metadata.
|
|
15
|
+
- Added JSR publication support for `@braian-quintian/express-version-api`.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Updated repository documentation to match the current runtime behavior and exports.
|
|
20
|
+
- Hardened release automation to validate npm authentication before publish.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Fixed `.npmrc` token interpolation to use `NODE_AUTH_TOKEN` in CI.
|
|
25
|
+
- Fixed GitHub Packages publication flow by using scoped package publish in workflow.
|
|
26
|
+
|
|
27
|
+
## [2.0.3] - 2026-02-27
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Updated release workflow with explicit npm authentication verification.
|
|
32
|
+
|
|
33
|
+
## [2.0.0] - 2026-02-27
|
|
34
|
+
|
|
35
|
+
### Removed
|
|
36
|
+
|
|
37
|
+
- Removed deprecated `createVersionMiddleware` API.
|
|
38
|
+
- Removed CommonJS default export path; CJS now uses named exports only.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- `test:types` now validates `test-types/**/*.test-d.ts` and runs in `validate`/CI.
|
|
43
|
+
- CI now targets `develop` and `main`, and PR checks use the actual base branch.
|
|
44
|
+
- `npm test` no longer writes coverage artifacts by default.
|
|
45
|
+
- Pre-commit hook now runs `lint-staged`.
|
|
46
|
+
- TypeScript incremental metadata moved outside publish output.
|
|
47
|
+
- Removed unused dev dependencies: `@types/semver`, `ts-node`.
|
|
48
|
+
- Updated README and package docs to match runtime behavior.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- `fallbackStrategy` is now validated at config resolution.
|
|
53
|
+
- Async `defaultHandler` errors are forwarded to Express `next(error)`.
|
|
54
|
+
- Caret matching for `0.x` follows semver-compatible constraints.
|
|
55
|
+
- `missingVersionMessage` from config is now applied in responses.
|
|
56
|
+
- Path extraction is stable with global/sticky regex (`lastIndex` handling).
|
|
57
|
+
- `req.versionInfo.source` now preserves the original extraction source in fallback paths.
|
|
58
|
+
|
|
59
|
+
## [1.0.6] - 2025-02-01
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- Stable baseline release published to npm.
|
package/README.md
CHANGED
|
@@ -1,162 +1,303 @@
|
|
|
1
|
-
#
|
|
1
|
+
# express-version-api
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/express-version-api)
|
|
4
|
-
[](https://nodejs.org)
|
|
4
|
+
[](https://www.npmjs.com/package/express-version-api)
|
|
5
|
+
[](https://github.com/Braian-Quintian/express-version-api/actions)
|
|
6
|
+
[](./LICENSE)
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
Version-aware middleware for Express.js APIs with semantic version matching.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
## Why use this
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
- Routes requests by semantic version (`exact`, `~`, `^`).
|
|
13
|
+
- Supports extraction from header, query, path, or custom function.
|
|
14
|
+
- Handles missing or unmatched versions with configurable fallback strategies.
|
|
15
|
+
- Exposes `req.versionInfo` metadata for observability and debugging.
|
|
16
|
+
- Fully typed for TypeScript projects.
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
## Installation
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
### npm
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## ✨ Features
|
|
24
|
-
|
|
25
|
-
- ✅ Version-based routing (`^`, `~`, exact versions)
|
|
26
|
-
- ✅ Automatic fallback to default or latest version
|
|
27
|
-
- ✅ Fully compatible with Express middleware
|
|
28
|
-
- ✅ Well-tested with Jest and Supertest
|
|
29
|
-
|
|
30
|
-
---
|
|
22
|
+
```bash
|
|
23
|
+
npm install express-version-api
|
|
24
|
+
```
|
|
31
25
|
|
|
32
|
-
|
|
26
|
+
### JSR
|
|
33
27
|
|
|
34
28
|
```bash
|
|
35
|
-
|
|
29
|
+
deno add jsr:@braian-quintian/express-version-api
|
|
36
30
|
```
|
|
37
31
|
|
|
38
|
-
|
|
32
|
+
Requirements:
|
|
39
33
|
|
|
40
|
-
|
|
34
|
+
- Node.js >= 18
|
|
35
|
+
- Express >= 4
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
const express = require("express");
|
|
44
|
-
const versionApi = require("express-version-api");
|
|
45
|
-
const app = express();
|
|
37
|
+
## Quick Start
|
|
46
38
|
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
```ts
|
|
40
|
+
import express from 'express';
|
|
41
|
+
import { versioningMiddleware } from 'express-version-api';
|
|
42
|
+
|
|
43
|
+
const app = express();
|
|
49
44
|
|
|
50
45
|
app.get(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
'/api/users',
|
|
47
|
+
versioningMiddleware({
|
|
48
|
+
'^1': (_req, res) => res.json({ version: '1.x', users: [] }),
|
|
49
|
+
'^2': (_req, res) => res.json({ version: '2.x', users: [], meta: { total: 0 } }),
|
|
50
|
+
'3.0.0': (_req, res) => res.json({ version: '3.0.0', users: [], paging: null }),
|
|
55
51
|
})
|
|
56
52
|
);
|
|
57
53
|
|
|
58
|
-
app.
|
|
59
|
-
|
|
54
|
+
app.use((error, _req, res, _next) => {
|
|
55
|
+
res.status(500).json({ error: error.message });
|
|
60
56
|
});
|
|
61
57
|
```
|
|
62
58
|
|
|
63
|
-
|
|
59
|
+
Request examples:
|
|
64
60
|
|
|
65
|
-
|
|
61
|
+
```bash
|
|
62
|
+
curl -H "Accept-Version: 1.2.0" http://localhost:3000/api/users
|
|
63
|
+
curl -H "Accept-Version: 2.5.1" http://localhost:3000/api/users
|
|
64
|
+
curl -H "Accept-Version: 3.0.0" http://localhost:3000/api/users
|
|
65
|
+
```
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
## Import Modes
|
|
68
|
+
|
|
69
|
+
ESM (default + named):
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import versioningMiddleware, { parseVersion } from 'express-version-api';
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
CommonJS (named only):
|
|
72
76
|
|
|
73
77
|
```js
|
|
74
|
-
|
|
75
|
-
"/api",
|
|
76
|
-
versionApi({
|
|
77
|
-
"^1.0.0": handlerV1,
|
|
78
|
-
"~2.1.0": handlerV2,
|
|
79
|
-
"3.0.0": handlerV3,
|
|
80
|
-
})
|
|
81
|
-
);
|
|
78
|
+
const { versioningMiddleware, parseVersion } = require('express-version-api');
|
|
82
79
|
```
|
|
83
80
|
|
|
84
|
-
|
|
81
|
+
## Version Matching Rules
|
|
85
82
|
|
|
86
|
-
|
|
83
|
+
Supported handler keys:
|
|
87
84
|
|
|
88
|
-
|
|
85
|
+
- Exact: `1.2.3`
|
|
86
|
+
- Caret: `^1`, `^1.2`, `^1.2.3`
|
|
87
|
+
- Tilde: `~1.2`, `~1.2.3`
|
|
89
88
|
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
Matching priority when multiple handlers match:
|
|
90
|
+
|
|
91
|
+
1. Exact
|
|
92
|
+
2. Tilde
|
|
93
|
+
3. Caret
|
|
94
|
+
|
|
95
|
+
Semantics:
|
|
96
|
+
|
|
97
|
+
- Exact: must match the same `major.minor.patch`.
|
|
98
|
+
- Tilde (`~`): same major and minor, patch must be `>=` requested patch.
|
|
99
|
+
- Caret (`^`): semver-compatible range.
|
|
100
|
+
- Caret with `0.x` follows strict semver-compatible behavior.
|
|
101
|
+
|
|
102
|
+
Examples:
|
|
103
|
+
|
|
104
|
+
- `^1.2.3` matches `1.2.3`, `1.3.0`, `1.9.9`, but not `2.0.0`.
|
|
105
|
+
- `^0.2.3` matches `0.2.3` to `0.2.x`, but not `0.3.0`.
|
|
106
|
+
- `~2.1.4` matches `2.1.4` to `2.1.x`, but not `2.2.0`.
|
|
107
|
+
|
|
108
|
+
## How Version Extraction Works
|
|
109
|
+
|
|
110
|
+
The middleware resolves version in this order:
|
|
111
|
+
|
|
112
|
+
1. `req.version` if already set by upstream middleware (source = `custom`)
|
|
113
|
+
2. Configured extraction sources in order (`extraction.sources`)
|
|
114
|
+
|
|
115
|
+
Default source order:
|
|
116
|
+
|
|
117
|
+
1. Header: `accept-version`
|
|
118
|
+
2. Query: `v`
|
|
119
|
+
|
|
120
|
+
You can configure `header`, `query`, `path`, and `custom` extractors.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
versioningMiddleware(
|
|
124
|
+
{
|
|
125
|
+
'^1': v1Handler,
|
|
126
|
+
'^2': v2Handler,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
extraction: {
|
|
130
|
+
sources: ['header', 'query', 'path', 'custom'],
|
|
131
|
+
header: { name: 'x-api-version' },
|
|
132
|
+
query: { name: 'version' },
|
|
133
|
+
path: { pattern: /\/api\/v(\d+(?:\.\d+)?(?:\.\d+)?)\// },
|
|
134
|
+
custom: (req) => (req.hostname.startsWith('v2.') ? '2.0.0' : undefined),
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
);
|
|
92
138
|
```
|
|
93
139
|
|
|
94
|
-
|
|
140
|
+
## Fallback Strategies
|
|
95
141
|
|
|
96
|
-
|
|
142
|
+
Fallback is used when no handler matches, and also when version is missing with `requireVersion: false`.
|
|
97
143
|
|
|
98
|
-
|
|
99
|
-
|
|
144
|
+
- `none`: respond with `VERSION_NOT_FOUND`.
|
|
145
|
+
- `latest`: execute the handler with the highest available version.
|
|
146
|
+
- `default`: execute `defaultHandler` if provided, otherwise fallback to `latest`.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
versioningMiddleware(handlers, {
|
|
150
|
+
fallbackStrategy: 'default',
|
|
151
|
+
defaultHandler: (_req, res) => {
|
|
152
|
+
res.status(200).json({ fallback: true });
|
|
153
|
+
},
|
|
154
|
+
});
|
|
100
155
|
```
|
|
101
156
|
|
|
102
|
-
|
|
157
|
+
## Error Handling
|
|
103
158
|
|
|
104
|
-
|
|
159
|
+
Error codes emitted by the middleware:
|
|
105
160
|
|
|
106
|
-
|
|
161
|
+
- `MISSING_VERSION`
|
|
162
|
+
- `INVALID_VERSION_FORMAT`
|
|
163
|
+
- `VERSION_NOT_FOUND`
|
|
164
|
+
- `INVALID_CONFIGURATION`
|
|
165
|
+
- `INVALID_HANDLER`
|
|
107
166
|
|
|
108
|
-
|
|
167
|
+
Default status codes:
|
|
109
168
|
|
|
110
|
-
-
|
|
111
|
-
-
|
|
169
|
+
- Missing version: `422`
|
|
170
|
+
- Version not found: `422`
|
|
171
|
+
- Invalid version format: `400`
|
|
112
172
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
173
|
+
Default response shape:
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"error": "VERSION_NOT_FOUND",
|
|
178
|
+
"message": "No handler found for the requested API version",
|
|
179
|
+
"requestedVersion": "99.0.0",
|
|
180
|
+
"availableVersions": ["^1", "^2"]
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Configure custom messages/statuses and override handling via `onError`:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
versioningMiddleware(handlers, {
|
|
188
|
+
errorResponse: {
|
|
189
|
+
missingVersionStatus: 400,
|
|
190
|
+
missingVersionMessage: 'Version header is required',
|
|
191
|
+
versionNotFoundMessage: (version) => `Version ${version} is not supported`,
|
|
192
|
+
onError: (error, _req, res) => {
|
|
193
|
+
if (error.code === 'INVALID_VERSION_FORMAT') {
|
|
194
|
+
res.status(400).json({ code: error.code, detail: error.message });
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
});
|
|
122
201
|
```
|
|
123
202
|
|
|
124
|
-
|
|
203
|
+
## Request Metadata (`req.versionInfo`)
|
|
125
204
|
|
|
126
|
-
|
|
205
|
+
When `attachVersionInfo` is `true`, the middleware adds:
|
|
127
206
|
|
|
128
|
-
|
|
207
|
+
```ts
|
|
208
|
+
{
|
|
209
|
+
requested: string;
|
|
210
|
+
matched: string;
|
|
211
|
+
source: 'header' | 'query' | 'path' | 'custom';
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Notes:
|
|
216
|
+
|
|
217
|
+
- `source` preserves the real extraction source, including fallback scenarios.
|
|
218
|
+
- When no version was provided and fallback is used, `versionInfo` is not attached because `requested` is undefined.
|
|
219
|
+
|
|
220
|
+
## Runtime Defaults
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
{
|
|
224
|
+
extraction: {
|
|
225
|
+
sources: ['header', 'query'],
|
|
226
|
+
header: { name: 'accept-version' },
|
|
227
|
+
query: { name: 'v' },
|
|
228
|
+
path: { pattern: /\/v(\d+(?:\.\d+)?(?:\.\d+)?)\// },
|
|
229
|
+
custom: undefined
|
|
230
|
+
},
|
|
231
|
+
errorResponse: {
|
|
232
|
+
missingVersionStatus: 422,
|
|
233
|
+
versionNotFoundStatus: 422,
|
|
234
|
+
missingVersionMessage: 'API version is required',
|
|
235
|
+
versionNotFoundMessage: 'No handler found for the requested API version',
|
|
236
|
+
includeRequestedVersion: true,
|
|
237
|
+
onError: undefined
|
|
238
|
+
},
|
|
239
|
+
fallbackStrategy: 'default',
|
|
240
|
+
defaultHandler: undefined,
|
|
241
|
+
validateHandlers: true,
|
|
242
|
+
attachVersionInfo: true,
|
|
243
|
+
requireVersion: true
|
|
244
|
+
}
|
|
245
|
+
```
|
|
129
246
|
|
|
130
|
-
|
|
131
|
-
2. Otherwise, fallback to the latest available handler
|
|
132
|
-
3. If no handler matches, return `422 Unprocessable Entity`
|
|
247
|
+
## Public API
|
|
133
248
|
|
|
134
|
-
|
|
249
|
+
Main middleware:
|
|
135
250
|
|
|
136
|
-
|
|
251
|
+
- `versioningMiddleware`
|
|
137
252
|
|
|
138
|
-
|
|
253
|
+
Error helpers:
|
|
139
254
|
|
|
140
|
-
|
|
255
|
+
- `VersioningError`
|
|
256
|
+
- `createMissingVersionError`
|
|
257
|
+
- `createVersionNotFoundError`
|
|
258
|
+
- `createInvalidVersionFormatError`
|
|
259
|
+
- `createInvalidHandlerError`
|
|
260
|
+
- `createInvalidConfigurationError`
|
|
261
|
+
- `isVersioningError`
|
|
262
|
+
- `hasErrorCode`
|
|
141
263
|
|
|
142
|
-
|
|
264
|
+
Version utilities:
|
|
143
265
|
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
266
|
+
- `parseVersion`
|
|
267
|
+
- `parseVersionRange`
|
|
268
|
+
- `versionSatisfies`
|
|
269
|
+
- `compareVersions`
|
|
270
|
+
- `findLatestVersion`
|
|
271
|
+
- `isValidVersion`
|
|
272
|
+
- `isValidVersionRange`
|
|
273
|
+
- `normalizeVersion`
|
|
150
274
|
|
|
151
|
-
|
|
275
|
+
Config export:
|
|
152
276
|
|
|
153
|
-
|
|
277
|
+
- `DEFAULT_CONFIG`
|
|
278
|
+
|
|
279
|
+
## Breaking Changes (v2)
|
|
280
|
+
|
|
281
|
+
- Removed legacy `createVersionMiddleware` API.
|
|
282
|
+
- CommonJS default export was removed; CJS must use named imports.
|
|
283
|
+
|
|
284
|
+
## Development
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
npm install
|
|
288
|
+
npm run validate
|
|
289
|
+
```
|
|
154
290
|
|
|
155
|
-
|
|
156
|
-
Contact: [bquintian.developer@gmail.com](mailto:bquintian.developer@gmail.com)
|
|
291
|
+
Main scripts:
|
|
157
292
|
|
|
158
|
-
|
|
293
|
+
- `npm run typecheck`
|
|
294
|
+
- `npm run lint`
|
|
295
|
+
- `npm run format:check`
|
|
296
|
+
- `npm run test`
|
|
297
|
+
- `npm run test:coverage`
|
|
298
|
+
- `npm run build`
|
|
299
|
+
- `npm run test:types`
|
|
159
300
|
|
|
160
|
-
##
|
|
301
|
+
## License
|
|
161
302
|
|
|
162
|
-
|
|
303
|
+
MIT
|