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 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
- # 📦 express-version-api
1
+ # express-version-api
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/express-version-api.svg)](https://www.npmjs.com/package/express-version-api)
4
- [![Build Status](https://img.shields.io/github/actions/workflow/status/Braian-Quintian/express-version-api/test.yml)](https://github.com/Braian-Quintian/express-version-api/actions)
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE)
6
- [![Coverage Status](https://img.shields.io/codecov/c/github/Braian-Quintian/express-version-api)](https://codecov.io/gh/Braian-Quintian/express-version-api)
7
- [![Node.js](https://img.shields.io/badge/node-%3E=14.0.0-brightgreen)](https://nodejs.org)
4
+ [![npm downloads](https://img.shields.io/npm/dm/express-version-api.svg)](https://www.npmjs.com/package/express-version-api)
5
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/Braian-Quintian/express-version-api/ci.yml?branch=develop)](https://github.com/Braian-Quintian/express-version-api/actions)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
8
7
 
9
- > Versioned routing middleware for Express.js based on semantic versioning (`semver`).
8
+ Version-aware middleware for Express.js APIs with semantic version matching.
10
9
 
11
- ---
10
+ ## Why use this
12
11
 
13
- **⚠️ NOTE:** This package is intended for personal use and experimentation. It is **not recommended** for production use.
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
- ## 🚧 Project Status
20
+ ### npm
18
21
 
19
- This library is under **active development**. Features and improvements are being added frequently. Use at your own discretion in unstable environments.
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
- ## 📦 Installation
26
+ ### JSR
33
27
 
34
28
  ```bash
35
- npm install express-version-api
29
+ deno add jsr:@braian-quintian/express-version-api
36
30
  ```
37
31
 
38
- ---
32
+ Requirements:
39
33
 
40
- ## 🚀 Quick Usage
34
+ - Node.js >= 18
35
+ - Express >= 4
41
36
 
42
- ```js
43
- const express = require("express");
44
- const versionApi = require("express-version-api");
45
- const app = express();
37
+ ## Quick Start
46
38
 
47
- const handlerV1 = (req, res) => res.send("This is version 1.0.0");
48
- const handlerV2 = (req, res) => res.send("This is version 2.0.0");
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
- "/api",
52
- versionApi({
53
- "1.0.0": handlerV1,
54
- "2.0.0": handlerV2,
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.listen(3000, () => {
59
- console.log("Server running on port 3000");
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
- ## 🎯 Semver Support (`^`, `~`)
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
- | Symbol | Matches | Example Matches |
68
- | ------ | -------------------------- | ------------------ |
69
- | `^` | Major-compatible (`1.x.x`) | `^1.0.0` → `1.2.3` |
70
- | `~` | Minor-compatible (`2.1.x`) | `~2.1.0` → `2.1.4` |
71
- | Exact | Exact version only | `3.0.0` → `3.0.0` |
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
- app.get(
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
- ## 📥 Version Header
83
+ Supported handler keys:
87
84
 
88
- Clients should include the `Accept-Version` HTTP header in requests:
85
+ - Exact: `1.2.3`
86
+ - Caret: `^1`, `^1.2`, `^1.2.3`
87
+ - Tilde: `~1.2`, `~1.2.3`
89
88
 
90
- ```bash
91
- curl -H "Accept-Version: 1.0.0" http://localhost:3000/api
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
- ## 🧪 Running Tests
142
+ Fallback is used when no handler matches, and also when version is missing with `requireVersion: false`.
97
143
 
98
- ```bash
99
- npm run test
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
- Tests are powered by [Jest](https://jestjs.io/) and [Supertest](https://github.com/visionmedia/supertest). Full coverage is included.
157
+ ## Error Handling
103
158
 
104
- ---
159
+ Error codes emitted by the middleware:
105
160
 
106
- ## 🧩 API
161
+ - `MISSING_VERSION`
162
+ - `INVALID_VERSION_FORMAT`
163
+ - `VERSION_NOT_FOUND`
164
+ - `INVALID_CONFIGURATION`
165
+ - `INVALID_HANDLER`
107
166
 
108
- ### `versionApi(handlers, defaultHandler?)`
167
+ Default status codes:
109
168
 
110
- - `handlers`: Object with semver-style version strings as keys and Express handlers as values.
111
- - `defaultHandler`: Optional fallback if no version matches.
169
+ - Missing version: `422`
170
+ - Version not found: `422`
171
+ - Invalid version format: `400`
112
172
 
113
- ```js
114
- app.get(
115
- "/api",
116
- versionApi({
117
- "^1.0.0": v1Handler,
118
- "~2.0.0": v2Handler,
119
- "3.0.0": v3Handler,
120
- }, fallbackHandler)
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
- ## 🔄 Fallback Strategy
205
+ When `attachVersionInfo` is `true`, the middleware adds:
127
206
 
128
- If the version is not matched:
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
- 1. Use `defaultHandler` if defined
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
- ## 📚 Examples
251
+ - `versioningMiddleware`
137
252
 
138
- Check the `test/` directory for integration examples and how `^`, `~` and fallbacks work in practice.
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
- ## 🛣️ Roadmap
264
+ Version utilities:
143
265
 
144
- - [x] Basic versioning with `^`, `~`, exact
145
- - [x] Default and latest fallback
146
- - [ ] Advanced semver range support (planned)
147
- - [ ] Improved validation and DX
148
- - [ ] Full ESM and CJS dual package
149
- - [ ] Typed handler inference with TypeScript
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
- ## 🤝 Contributing
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
- Contributions, issues, and feature requests are welcome!
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
- ## 🛡 License
301
+ ## License
161
302
 
162
- This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
303
+ MIT