express-version-api 1.0.1 → 1.0.6
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/LICENSE +23 -21
- package/README.md +162 -146
- package/dist/cjs/index.js +11 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lib.js +209 -0
- package/dist/cjs/lib.js.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib.js +201 -0
- package/dist/esm/lib.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/lib.d.ts +137 -0
- package/package.json +42 -21
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -8
- package/dist/lib.d.ts +0 -15
- package/dist/lib.js +0 -147
package/LICENSE
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c)
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Braian Quintian
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
For more information, see <https://github.com/Braian-Quintian/express-version-api>
|
package/README.md
CHANGED
|
@@ -1,146 +1,162 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/express-version-api)
|
|
4
|
+
[](https://github.com/Braian-Quintian/express-version-api/actions)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://codecov.io/gh/Braian-Quintian/express-version-api)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
|
|
9
|
+
> Versioned routing middleware for Express.js based on semantic versioning (`semver`).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
**⚠️ NOTE:** This package is intended for personal use and experimentation. It is **not recommended** for production use.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 🚧 Project Status
|
|
18
|
+
|
|
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
|
+
---
|
|
31
|
+
|
|
32
|
+
## 📦 Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install express-version-api
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 🚀 Quick Usage
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
const express = require("express");
|
|
44
|
+
const versionApi = require("express-version-api");
|
|
45
|
+
const app = express();
|
|
46
|
+
|
|
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");
|
|
49
|
+
|
|
50
|
+
app.get(
|
|
51
|
+
"/api",
|
|
52
|
+
versionApi({
|
|
53
|
+
"1.0.0": handlerV1,
|
|
54
|
+
"2.0.0": handlerV2,
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
app.listen(3000, () => {
|
|
59
|
+
console.log("Server running on port 3000");
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 🎯 Semver Support (`^`, `~`)
|
|
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` |
|
|
72
|
+
|
|
73
|
+
```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
|
+
);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 📥 Version Header
|
|
87
|
+
|
|
88
|
+
Clients should include the `Accept-Version` HTTP header in requests:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
curl -H "Accept-Version: 1.0.0" http://localhost:3000/api
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 🧪 Running Tests
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm run test
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Tests are powered by [Jest](https://jestjs.io/) and [Supertest](https://github.com/visionmedia/supertest). Full coverage is included.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 🧩 API
|
|
107
|
+
|
|
108
|
+
### `versionApi(handlers, defaultHandler?)`
|
|
109
|
+
|
|
110
|
+
- `handlers`: Object with semver-style version strings as keys and Express handlers as values.
|
|
111
|
+
- `defaultHandler`: Optional fallback if no version matches.
|
|
112
|
+
|
|
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
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 🔄 Fallback Strategy
|
|
127
|
+
|
|
128
|
+
If the version is not matched:
|
|
129
|
+
|
|
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`
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 📚 Examples
|
|
137
|
+
|
|
138
|
+
Check the `test/` directory for integration examples and how `^`, `~` and fallbacks work in practice.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 🛣️ Roadmap
|
|
143
|
+
|
|
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
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 🤝 Contributing
|
|
154
|
+
|
|
155
|
+
Contributions, issues, and feature requests are welcome!
|
|
156
|
+
Contact: [bquintian.developer@gmail.com](mailto:bquintian.developer@gmail.com)
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 🛡 License
|
|
161
|
+
|
|
162
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
// Main entry point of the library.
|
|
8
|
+
// Re-exports the default export from lib.ts to support both ESM and CommonJS.
|
|
9
|
+
var lib_1 = require("./lib");
|
|
10
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(lib_1).default; } });
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;AAAA,mCAAmC;AACnC,8EAA8E;AAC9E,6BAAgC;AAAvB,+GAAA,OAAO,OAAA"}
|
package/dist/cjs/lib.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findLatestVersion = exports.versioningMiddleware = exports.executeDefaultHandler = exports.executeVersionHandler = exports.extractVersionFromRequest = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Attempts to extract a semantic version string from an Express request.
|
|
6
|
+
*
|
|
7
|
+
* This function checks the `Request` object for a version identifier in the following order:
|
|
8
|
+
* 1. `req.version` — typically set by middleware.
|
|
9
|
+
* 2. `req.headers['accept-version']` — often provided by clients via HTTP headers.
|
|
10
|
+
*
|
|
11
|
+
* If no version is found, it returns `false`.
|
|
12
|
+
*
|
|
13
|
+
* @param req - The Express request object.
|
|
14
|
+
* @returns The version string (e.g., "1.0.0"), or `false` if none is provided.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const version = extractVersionFromRequest(req);
|
|
19
|
+
* if (!version) {
|
|
20
|
+
* res.status(422).send("No version provided");
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
const extractVersionFromRequest = (req) => {
|
|
25
|
+
const sources = [req.version, req.headers["accept-version"]];
|
|
26
|
+
for (const source of sources) {
|
|
27
|
+
if (typeof source === "string")
|
|
28
|
+
return source;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
};
|
|
32
|
+
exports.extractVersionFromRequest = extractVersionFromRequest;
|
|
33
|
+
/**
|
|
34
|
+
* Executes a version handler if the client version matches the handler's semantic key.
|
|
35
|
+
*
|
|
36
|
+
* Determines compatibility between the given `handlerKey` (e.g., "^1", "~2.1", "3.0.0")
|
|
37
|
+
* and the `clientVersion` (e.g., "2.1.3") using simplified semantic version matching:
|
|
38
|
+
*
|
|
39
|
+
* - `^1` matches any version starting with "1" (e.g., "1.0.0", "1.9.5")
|
|
40
|
+
* - `~2.1` matches versions like "2.1.x" (e.g., "2.1.0", "2.1.9")
|
|
41
|
+
* - `"3.0.0"` matches exactly "3.0.0"
|
|
42
|
+
*
|
|
43
|
+
* If a match is found, the associated handler from `versionHandlers` is invoked.
|
|
44
|
+
*
|
|
45
|
+
* @param handlerKey - The version key used for comparison (e.g., "^1", "~2.1", "3.0.0").
|
|
46
|
+
* @param clientVersion - The version string from the client request (e.g., "2.1.3").
|
|
47
|
+
* @param versionHandlers - A mapping of version keys to Express request handlers.
|
|
48
|
+
* @param req - The Express request object.
|
|
49
|
+
* @param res - The Express response object.
|
|
50
|
+
* @param next - The Express next middleware function.
|
|
51
|
+
* @returns `true` if a compatible handler was executed, `false` otherwise.
|
|
52
|
+
*/
|
|
53
|
+
const executeVersionHandler = (handlerKey, clientVersion, versionHandlers, req, res, next) => {
|
|
54
|
+
const [clientMajor = "0", clientMinor = "0", clientPatch = "0"] = clientVersion.split(".");
|
|
55
|
+
let targetVersion;
|
|
56
|
+
let requestedVersion;
|
|
57
|
+
if (handlerKey.startsWith("~")) {
|
|
58
|
+
// "~2.1" → match major.minor
|
|
59
|
+
targetVersion = handlerKey.slice(1).split(".").slice(0, 2).join(".");
|
|
60
|
+
requestedVersion = `${clientMajor}.${clientMinor}`;
|
|
61
|
+
}
|
|
62
|
+
else if (handlerKey.startsWith("^")) {
|
|
63
|
+
// "^1" → match major only
|
|
64
|
+
targetVersion = handlerKey.slice(1).split(".")[0];
|
|
65
|
+
requestedVersion = clientMajor;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Exact match: "2.1.3"
|
|
69
|
+
targetVersion = handlerKey;
|
|
70
|
+
requestedVersion = `${clientMajor}.${clientMinor}.${clientPatch}`;
|
|
71
|
+
}
|
|
72
|
+
const handler = versionHandlers[handlerKey];
|
|
73
|
+
if (requestedVersion === targetVersion && typeof handler === "function") {
|
|
74
|
+
void handler(req, res, next);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
};
|
|
79
|
+
exports.executeVersionHandler = executeVersionHandler;
|
|
80
|
+
/**
|
|
81
|
+
* Executes the default handler when no version match is found.
|
|
82
|
+
*
|
|
83
|
+
* Priority order:
|
|
84
|
+
* 1. If a `defaultHandler` is provided, it is invoked immediately.
|
|
85
|
+
* 2. Otherwise, it tries to find and execute the handler for the latest version available.
|
|
86
|
+
* 3. If none is found, it responds with HTTP 422 and an optional custom error message.
|
|
87
|
+
*
|
|
88
|
+
* This ensures that version-based routing gracefully degrades to a default behavior
|
|
89
|
+
* or returns an informative error if no handler can be determined.
|
|
90
|
+
*
|
|
91
|
+
* @param defaultHandler - Optional fallback Express handler.
|
|
92
|
+
* @param versionHandlers - A map of version keys to handlers.
|
|
93
|
+
* @param keys - An array of version keys from the versionHandlers object.
|
|
94
|
+
* @param req - Express request object.
|
|
95
|
+
* @param res - Express response object.
|
|
96
|
+
* @param next - Express next middleware function.
|
|
97
|
+
* @param notFoundMessage - Optional custom error message for 422 responses.
|
|
98
|
+
*/
|
|
99
|
+
const executeDefaultHandler = (defaultHandler, versionHandlers, keys, req, res, next, notFoundMessage = "Unprocessable Entity: No valid version handler available.") => {
|
|
100
|
+
if (defaultHandler) {
|
|
101
|
+
void defaultHandler(req, res, next);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (keys.length === 0) {
|
|
105
|
+
res.status(422).send(notFoundMessage);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const latestVersionKey = (0, exports.findLatestVersion)(keys);
|
|
109
|
+
const fallbackHandler = latestVersionKey ? versionHandlers[latestVersionKey] : undefined;
|
|
110
|
+
if (fallbackHandler) {
|
|
111
|
+
void fallbackHandler(req, res, next);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
res.status(422).send(notFoundMessage);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
exports.executeDefaultHandler = executeDefaultHandler;
|
|
118
|
+
/**
|
|
119
|
+
* Middleware for handling version-based routing in Express.
|
|
120
|
+
*
|
|
121
|
+
* This middleware inspects the incoming request for a semantic version (e.g., "1.0.0", "~2.1", "^3")
|
|
122
|
+
* and delegates the request to the appropriate handler defined in `versionHandlers`.
|
|
123
|
+
*
|
|
124
|
+
* Matching priority:
|
|
125
|
+
* 1. If a version is found in the request, attempt to match a handler.
|
|
126
|
+
* 2. If no match is found, invoke the `defaultHandler` (if provided).
|
|
127
|
+
* 3. If no default handler exists, fall back to the latest version handler.
|
|
128
|
+
* 4. If none is available, respond with HTTP 422.
|
|
129
|
+
*
|
|
130
|
+
* @param versionHandlers - A map of semantic version keys to Express handlers.
|
|
131
|
+
* @param defaultHandler - Optional fallback handler if no version matches.
|
|
132
|
+
* @returns An Express middleware function.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* app.use(
|
|
137
|
+
* versioningMiddleware({
|
|
138
|
+
* "^1": handlerForV1,
|
|
139
|
+
* "~2.1": handlerForV2_1,
|
|
140
|
+
* "3.0.0": handlerForV3
|
|
141
|
+
* })
|
|
142
|
+
* );
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
const versioningMiddleware = (versionHandlers, defaultHandler) => {
|
|
146
|
+
if (!versionHandlers ||
|
|
147
|
+
typeof versionHandlers !== "object" ||
|
|
148
|
+
Array.isArray(versionHandlers)) {
|
|
149
|
+
throw new Error("Invalid argument: 'versionHandlers' must be a non-array object.");
|
|
150
|
+
}
|
|
151
|
+
return (req, res, next) => {
|
|
152
|
+
const clientVersion = (0, exports.extractVersionFromRequest)(req);
|
|
153
|
+
const handlerKeys = Object.keys(versionHandlers);
|
|
154
|
+
// No version provided by the client
|
|
155
|
+
if (!clientVersion) {
|
|
156
|
+
res.status(422).send("Unprocessable Entity: No version provided.");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// Try to match a handler by version
|
|
160
|
+
for (const handlerKey of handlerKeys) {
|
|
161
|
+
const matched = (0, exports.executeVersionHandler)(handlerKey, clientVersion, versionHandlers, req, res, next);
|
|
162
|
+
if (matched)
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// Fallback to default or latest handler
|
|
166
|
+
(0, exports.executeDefaultHandler)(defaultHandler, versionHandlers, handlerKeys, req, res, next);
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
exports.versioningMiddleware = versioningMiddleware;
|
|
170
|
+
/**
|
|
171
|
+
* Determines the latest semantic version from a list of version strings.
|
|
172
|
+
*
|
|
173
|
+
* This function removes any leading semantic operators (`^`, `~`) and
|
|
174
|
+
* compares versions based on major, minor, and patch numbers.
|
|
175
|
+
*
|
|
176
|
+
* @param versions - An array of version keys, possibly including "^" or "~" prefixes.
|
|
177
|
+
* @returns The latest normalized version string (e.g. "3.2.0"), or `null` if the array is empty.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* const latest = findLatestVersion(["^1", "~2.3", "3.0.0"]);
|
|
182
|
+
* latest === "3.0.0"
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
const findLatestVersion = (versions) => {
|
|
186
|
+
if (!Array.isArray(versions) || versions.length === 0) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const normalize = (version) => {
|
|
190
|
+
const cleaned = version.replace(/^[\^~]/, "");
|
|
191
|
+
const parts = cleaned.split(".").map((n) => parseInt(n, 10));
|
|
192
|
+
while (parts.length < 3)
|
|
193
|
+
parts.push(0); // pad missing parts with 0
|
|
194
|
+
return parts.slice(0, 3);
|
|
195
|
+
};
|
|
196
|
+
const sorted = [...versions].sort((a, b) => {
|
|
197
|
+
const [aMajor, aMinor, aPatch] = normalize(a);
|
|
198
|
+
const [bMajor, bMinor, bPatch] = normalize(b);
|
|
199
|
+
if (aMajor !== bMajor)
|
|
200
|
+
return bMajor - aMajor;
|
|
201
|
+
if (aMinor !== bMinor)
|
|
202
|
+
return bMinor - aMinor;
|
|
203
|
+
return bPatch - aPatch;
|
|
204
|
+
});
|
|
205
|
+
return sorted.length > 0 ? sorted[0].replace(/^[\^~]/, "") : null;
|
|
206
|
+
};
|
|
207
|
+
exports.findLatestVersion = findLatestVersion;
|
|
208
|
+
exports.default = exports.versioningMiddleware;
|
|
209
|
+
//# sourceMappingURL=lib.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../src/lib.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,8EAA8E;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC"}
|
package/dist/esm/lib.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attempts to extract a semantic version string from an Express request.
|
|
3
|
+
*
|
|
4
|
+
* This function checks the `Request` object for a version identifier in the following order:
|
|
5
|
+
* 1. `req.version` — typically set by middleware.
|
|
6
|
+
* 2. `req.headers['accept-version']` — often provided by clients via HTTP headers.
|
|
7
|
+
*
|
|
8
|
+
* If no version is found, it returns `false`.
|
|
9
|
+
*
|
|
10
|
+
* @param req - The Express request object.
|
|
11
|
+
* @returns The version string (e.g., "1.0.0"), or `false` if none is provided.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const version = extractVersionFromRequest(req);
|
|
16
|
+
* if (!version) {
|
|
17
|
+
* res.status(422).send("No version provided");
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export const extractVersionFromRequest = (req) => {
|
|
22
|
+
const sources = [req.version, req.headers["accept-version"]];
|
|
23
|
+
for (const source of sources) {
|
|
24
|
+
if (typeof source === "string")
|
|
25
|
+
return source;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Executes a version handler if the client version matches the handler's semantic key.
|
|
31
|
+
*
|
|
32
|
+
* Determines compatibility between the given `handlerKey` (e.g., "^1", "~2.1", "3.0.0")
|
|
33
|
+
* and the `clientVersion` (e.g., "2.1.3") using simplified semantic version matching:
|
|
34
|
+
*
|
|
35
|
+
* - `^1` matches any version starting with "1" (e.g., "1.0.0", "1.9.5")
|
|
36
|
+
* - `~2.1` matches versions like "2.1.x" (e.g., "2.1.0", "2.1.9")
|
|
37
|
+
* - `"3.0.0"` matches exactly "3.0.0"
|
|
38
|
+
*
|
|
39
|
+
* If a match is found, the associated handler from `versionHandlers` is invoked.
|
|
40
|
+
*
|
|
41
|
+
* @param handlerKey - The version key used for comparison (e.g., "^1", "~2.1", "3.0.0").
|
|
42
|
+
* @param clientVersion - The version string from the client request (e.g., "2.1.3").
|
|
43
|
+
* @param versionHandlers - A mapping of version keys to Express request handlers.
|
|
44
|
+
* @param req - The Express request object.
|
|
45
|
+
* @param res - The Express response object.
|
|
46
|
+
* @param next - The Express next middleware function.
|
|
47
|
+
* @returns `true` if a compatible handler was executed, `false` otherwise.
|
|
48
|
+
*/
|
|
49
|
+
export const executeVersionHandler = (handlerKey, clientVersion, versionHandlers, req, res, next) => {
|
|
50
|
+
const [clientMajor = "0", clientMinor = "0", clientPatch = "0"] = clientVersion.split(".");
|
|
51
|
+
let targetVersion;
|
|
52
|
+
let requestedVersion;
|
|
53
|
+
if (handlerKey.startsWith("~")) {
|
|
54
|
+
// "~2.1" → match major.minor
|
|
55
|
+
targetVersion = handlerKey.slice(1).split(".").slice(0, 2).join(".");
|
|
56
|
+
requestedVersion = `${clientMajor}.${clientMinor}`;
|
|
57
|
+
}
|
|
58
|
+
else if (handlerKey.startsWith("^")) {
|
|
59
|
+
// "^1" → match major only
|
|
60
|
+
targetVersion = handlerKey.slice(1).split(".")[0];
|
|
61
|
+
requestedVersion = clientMajor;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Exact match: "2.1.3"
|
|
65
|
+
targetVersion = handlerKey;
|
|
66
|
+
requestedVersion = `${clientMajor}.${clientMinor}.${clientPatch}`;
|
|
67
|
+
}
|
|
68
|
+
const handler = versionHandlers[handlerKey];
|
|
69
|
+
if (requestedVersion === targetVersion && typeof handler === "function") {
|
|
70
|
+
void handler(req, res, next);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Executes the default handler when no version match is found.
|
|
77
|
+
*
|
|
78
|
+
* Priority order:
|
|
79
|
+
* 1. If a `defaultHandler` is provided, it is invoked immediately.
|
|
80
|
+
* 2. Otherwise, it tries to find and execute the handler for the latest version available.
|
|
81
|
+
* 3. If none is found, it responds with HTTP 422 and an optional custom error message.
|
|
82
|
+
*
|
|
83
|
+
* This ensures that version-based routing gracefully degrades to a default behavior
|
|
84
|
+
* or returns an informative error if no handler can be determined.
|
|
85
|
+
*
|
|
86
|
+
* @param defaultHandler - Optional fallback Express handler.
|
|
87
|
+
* @param versionHandlers - A map of version keys to handlers.
|
|
88
|
+
* @param keys - An array of version keys from the versionHandlers object.
|
|
89
|
+
* @param req - Express request object.
|
|
90
|
+
* @param res - Express response object.
|
|
91
|
+
* @param next - Express next middleware function.
|
|
92
|
+
* @param notFoundMessage - Optional custom error message for 422 responses.
|
|
93
|
+
*/
|
|
94
|
+
export const executeDefaultHandler = (defaultHandler, versionHandlers, keys, req, res, next, notFoundMessage = "Unprocessable Entity: No valid version handler available.") => {
|
|
95
|
+
if (defaultHandler) {
|
|
96
|
+
void defaultHandler(req, res, next);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (keys.length === 0) {
|
|
100
|
+
res.status(422).send(notFoundMessage);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const latestVersionKey = findLatestVersion(keys);
|
|
104
|
+
const fallbackHandler = latestVersionKey ? versionHandlers[latestVersionKey] : undefined;
|
|
105
|
+
if (fallbackHandler) {
|
|
106
|
+
void fallbackHandler(req, res, next);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
res.status(422).send(notFoundMessage);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Middleware for handling version-based routing in Express.
|
|
114
|
+
*
|
|
115
|
+
* This middleware inspects the incoming request for a semantic version (e.g., "1.0.0", "~2.1", "^3")
|
|
116
|
+
* and delegates the request to the appropriate handler defined in `versionHandlers`.
|
|
117
|
+
*
|
|
118
|
+
* Matching priority:
|
|
119
|
+
* 1. If a version is found in the request, attempt to match a handler.
|
|
120
|
+
* 2. If no match is found, invoke the `defaultHandler` (if provided).
|
|
121
|
+
* 3. If no default handler exists, fall back to the latest version handler.
|
|
122
|
+
* 4. If none is available, respond with HTTP 422.
|
|
123
|
+
*
|
|
124
|
+
* @param versionHandlers - A map of semantic version keys to Express handlers.
|
|
125
|
+
* @param defaultHandler - Optional fallback handler if no version matches.
|
|
126
|
+
* @returns An Express middleware function.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* app.use(
|
|
131
|
+
* versioningMiddleware({
|
|
132
|
+
* "^1": handlerForV1,
|
|
133
|
+
* "~2.1": handlerForV2_1,
|
|
134
|
+
* "3.0.0": handlerForV3
|
|
135
|
+
* })
|
|
136
|
+
* );
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export const versioningMiddleware = (versionHandlers, defaultHandler) => {
|
|
140
|
+
if (!versionHandlers ||
|
|
141
|
+
typeof versionHandlers !== "object" ||
|
|
142
|
+
Array.isArray(versionHandlers)) {
|
|
143
|
+
throw new Error("Invalid argument: 'versionHandlers' must be a non-array object.");
|
|
144
|
+
}
|
|
145
|
+
return (req, res, next) => {
|
|
146
|
+
const clientVersion = extractVersionFromRequest(req);
|
|
147
|
+
const handlerKeys = Object.keys(versionHandlers);
|
|
148
|
+
// No version provided by the client
|
|
149
|
+
if (!clientVersion) {
|
|
150
|
+
res.status(422).send("Unprocessable Entity: No version provided.");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// Try to match a handler by version
|
|
154
|
+
for (const handlerKey of handlerKeys) {
|
|
155
|
+
const matched = executeVersionHandler(handlerKey, clientVersion, versionHandlers, req, res, next);
|
|
156
|
+
if (matched)
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// Fallback to default or latest handler
|
|
160
|
+
executeDefaultHandler(defaultHandler, versionHandlers, handlerKeys, req, res, next);
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Determines the latest semantic version from a list of version strings.
|
|
165
|
+
*
|
|
166
|
+
* This function removes any leading semantic operators (`^`, `~`) and
|
|
167
|
+
* compares versions based on major, minor, and patch numbers.
|
|
168
|
+
*
|
|
169
|
+
* @param versions - An array of version keys, possibly including "^" or "~" prefixes.
|
|
170
|
+
* @returns The latest normalized version string (e.g. "3.2.0"), or `null` if the array is empty.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* const latest = findLatestVersion(["^1", "~2.3", "3.0.0"]);
|
|
175
|
+
* latest === "3.0.0"
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
export const findLatestVersion = (versions) => {
|
|
179
|
+
if (!Array.isArray(versions) || versions.length === 0) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const normalize = (version) => {
|
|
183
|
+
const cleaned = version.replace(/^[\^~]/, "");
|
|
184
|
+
const parts = cleaned.split(".").map((n) => parseInt(n, 10));
|
|
185
|
+
while (parts.length < 3)
|
|
186
|
+
parts.push(0); // pad missing parts with 0
|
|
187
|
+
return parts.slice(0, 3);
|
|
188
|
+
};
|
|
189
|
+
const sorted = [...versions].sort((a, b) => {
|
|
190
|
+
const [aMajor, aMinor, aPatch] = normalize(a);
|
|
191
|
+
const [bMajor, bMinor, bPatch] = normalize(b);
|
|
192
|
+
if (aMajor !== bMajor)
|
|
193
|
+
return bMajor - aMajor;
|
|
194
|
+
if (aMinor !== bMinor)
|
|
195
|
+
return bMinor - aMinor;
|
|
196
|
+
return bPatch - aPatch;
|
|
197
|
+
});
|
|
198
|
+
return sorted.length > 0 ? sorted[0].replace(/^[\^~]/, "") : null;
|
|
199
|
+
};
|
|
200
|
+
export default versioningMiddleware;
|
|
201
|
+
//# sourceMappingURL=lib.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../src/lib.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./lib";
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { type Request, type Response, type NextFunction, type RequestHandler } from "express";
|
|
2
|
+
/**
|
|
3
|
+
* An object that maps semantic version strings to specific request handlers.
|
|
4
|
+
*
|
|
5
|
+
* This is the core input to the `versioningMiddleware` function. Each key must be a valid
|
|
6
|
+
* version string, such as `"1.0.0"`, `"^1"`, or `"~2.1"`. These prefixes (`^` and `~`) allow
|
|
7
|
+
* matching of major/minor version ranges, similar to how they work in `package.json`.
|
|
8
|
+
*
|
|
9
|
+
* The value for each key is an Express-compatible handler that should respond to requests
|
|
10
|
+
* targeting that version.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const handlers: VersionHandlers = {
|
|
15
|
+
* "^1": (req, res) => res.send("v1.x handler"),
|
|
16
|
+
* "~2.1": (req, res) => res.send("v2.1.x handler"),
|
|
17
|
+
* "3.0.0": (req, res) => res.send("exact v3.0.0 handler"),
|
|
18
|
+
* };
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export interface VersionHandlers {
|
|
22
|
+
[version: string]: RequestHandler;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* An extended Express `Request` object that optionally includes a `version` property.
|
|
26
|
+
* This allows support for custom middleware.
|
|
27
|
+
*/
|
|
28
|
+
interface VersionedRequest extends Request {
|
|
29
|
+
version?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Attempts to extract a semantic version string from an Express request.
|
|
33
|
+
*
|
|
34
|
+
* This function checks the `Request` object for a version identifier in the following order:
|
|
35
|
+
* 1. `req.version` — typically set by middleware.
|
|
36
|
+
* 2. `req.headers['accept-version']` — often provided by clients via HTTP headers.
|
|
37
|
+
*
|
|
38
|
+
* If no version is found, it returns `false`.
|
|
39
|
+
*
|
|
40
|
+
* @param req - The Express request object.
|
|
41
|
+
* @returns The version string (e.g., "1.0.0"), or `false` if none is provided.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* const version = extractVersionFromRequest(req);
|
|
46
|
+
* if (!version) {
|
|
47
|
+
* res.status(422).send("No version provided");
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare const extractVersionFromRequest: (req: VersionedRequest) => string | false;
|
|
52
|
+
/**
|
|
53
|
+
* Executes a version handler if the client version matches the handler's semantic key.
|
|
54
|
+
*
|
|
55
|
+
* Determines compatibility between the given `handlerKey` (e.g., "^1", "~2.1", "3.0.0")
|
|
56
|
+
* and the `clientVersion` (e.g., "2.1.3") using simplified semantic version matching:
|
|
57
|
+
*
|
|
58
|
+
* - `^1` matches any version starting with "1" (e.g., "1.0.0", "1.9.5")
|
|
59
|
+
* - `~2.1` matches versions like "2.1.x" (e.g., "2.1.0", "2.1.9")
|
|
60
|
+
* - `"3.0.0"` matches exactly "3.0.0"
|
|
61
|
+
*
|
|
62
|
+
* If a match is found, the associated handler from `versionHandlers` is invoked.
|
|
63
|
+
*
|
|
64
|
+
* @param handlerKey - The version key used for comparison (e.g., "^1", "~2.1", "3.0.0").
|
|
65
|
+
* @param clientVersion - The version string from the client request (e.g., "2.1.3").
|
|
66
|
+
* @param versionHandlers - A mapping of version keys to Express request handlers.
|
|
67
|
+
* @param req - The Express request object.
|
|
68
|
+
* @param res - The Express response object.
|
|
69
|
+
* @param next - The Express next middleware function.
|
|
70
|
+
* @returns `true` if a compatible handler was executed, `false` otherwise.
|
|
71
|
+
*/
|
|
72
|
+
export declare const executeVersionHandler: (handlerKey: string, clientVersion: string, versionHandlers: VersionHandlers, req: Request, res: Response, next: NextFunction) => boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Executes the default handler when no version match is found.
|
|
75
|
+
*
|
|
76
|
+
* Priority order:
|
|
77
|
+
* 1. If a `defaultHandler` is provided, it is invoked immediately.
|
|
78
|
+
* 2. Otherwise, it tries to find and execute the handler for the latest version available.
|
|
79
|
+
* 3. If none is found, it responds with HTTP 422 and an optional custom error message.
|
|
80
|
+
*
|
|
81
|
+
* This ensures that version-based routing gracefully degrades to a default behavior
|
|
82
|
+
* or returns an informative error if no handler can be determined.
|
|
83
|
+
*
|
|
84
|
+
* @param defaultHandler - Optional fallback Express handler.
|
|
85
|
+
* @param versionHandlers - A map of version keys to handlers.
|
|
86
|
+
* @param keys - An array of version keys from the versionHandlers object.
|
|
87
|
+
* @param req - Express request object.
|
|
88
|
+
* @param res - Express response object.
|
|
89
|
+
* @param next - Express next middleware function.
|
|
90
|
+
* @param notFoundMessage - Optional custom error message for 422 responses.
|
|
91
|
+
*/
|
|
92
|
+
export declare const executeDefaultHandler: (defaultHandler: RequestHandler | undefined, versionHandlers: VersionHandlers, keys: string[], req: Request, res: Response, next: NextFunction, notFoundMessage?: string) => void;
|
|
93
|
+
/**
|
|
94
|
+
* Middleware for handling version-based routing in Express.
|
|
95
|
+
*
|
|
96
|
+
* This middleware inspects the incoming request for a semantic version (e.g., "1.0.0", "~2.1", "^3")
|
|
97
|
+
* and delegates the request to the appropriate handler defined in `versionHandlers`.
|
|
98
|
+
*
|
|
99
|
+
* Matching priority:
|
|
100
|
+
* 1. If a version is found in the request, attempt to match a handler.
|
|
101
|
+
* 2. If no match is found, invoke the `defaultHandler` (if provided).
|
|
102
|
+
* 3. If no default handler exists, fall back to the latest version handler.
|
|
103
|
+
* 4. If none is available, respond with HTTP 422.
|
|
104
|
+
*
|
|
105
|
+
* @param versionHandlers - A map of semantic version keys to Express handlers.
|
|
106
|
+
* @param defaultHandler - Optional fallback handler if no version matches.
|
|
107
|
+
* @returns An Express middleware function.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* app.use(
|
|
112
|
+
* versioningMiddleware({
|
|
113
|
+
* "^1": handlerForV1,
|
|
114
|
+
* "~2.1": handlerForV2_1,
|
|
115
|
+
* "3.0.0": handlerForV3
|
|
116
|
+
* })
|
|
117
|
+
* );
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export declare const versioningMiddleware: (versionHandlers: VersionHandlers, defaultHandler?: RequestHandler) => RequestHandler;
|
|
121
|
+
/**
|
|
122
|
+
* Determines the latest semantic version from a list of version strings.
|
|
123
|
+
*
|
|
124
|
+
* This function removes any leading semantic operators (`^`, `~`) and
|
|
125
|
+
* compares versions based on major, minor, and patch numbers.
|
|
126
|
+
*
|
|
127
|
+
* @param versions - An array of version keys, possibly including "^" or "~" prefixes.
|
|
128
|
+
* @returns The latest normalized version string (e.g. "3.2.0"), or `null` if the array is empty.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* const latest = findLatestVersion(["^1", "~2.3", "3.0.0"]);
|
|
133
|
+
* latest === "3.0.0"
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export declare const findLatestVersion: (versions: string[]) => string | null;
|
|
137
|
+
export default versioningMiddleware;
|
package/package.json
CHANGED
|
@@ -1,41 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-version-api",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"version": "1.0.6",
|
|
4
|
+
"description": "Middleware for versioning routes/APIs in Express.js using semantic versioning like ^1.0 or ~1.2.",
|
|
5
5
|
"author": "Braian Quintian",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"homepage": "https://github.com/Braian-Quintian/express-version-
|
|
7
|
+
"homepage": "https://github.com/Braian-Quintian/express-version-api#readme",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/Braian-Quintian/express-version-api"
|
|
10
|
+
"url": "git+https://github.com/Braian-Quintian/express-version-api.git"
|
|
11
11
|
},
|
|
12
12
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/Braian-Quintian/express-version-
|
|
13
|
+
"url": "https://github.com/Braian-Quintian/express-version-api/issues"
|
|
14
|
+
},
|
|
15
|
+
"main": "dist/cjs/index.js",
|
|
16
|
+
"module": "dist/esm/index.js",
|
|
17
|
+
"types": "dist/types/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"require": "./dist/cjs/index.js",
|
|
21
|
+
"import": "./dist/esm/index.js",
|
|
22
|
+
"types": "./dist/types/index.d.ts"
|
|
23
|
+
}
|
|
14
24
|
},
|
|
15
|
-
"main": "dist/index.js",
|
|
16
25
|
"engines": {
|
|
17
26
|
"node": ">=20.0.0"
|
|
18
27
|
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
19
31
|
"scripts": {
|
|
20
|
-
"build": "
|
|
21
|
-
"
|
|
32
|
+
"build:esm": "tsc --project tsconfig.esm.json",
|
|
33
|
+
"build:cjs": "tsc --project tsconfig.cjs.json",
|
|
34
|
+
"build": "npm run build:esm && npm run build:cjs",
|
|
35
|
+
"clean": "rm -rf dist",
|
|
36
|
+
"prepare": "npm run clean && npm run build",
|
|
37
|
+
"lint": "eslint src --ext .ts",
|
|
38
|
+
"test": "jest --config jest.config.ts --verbose"
|
|
22
39
|
},
|
|
23
40
|
"keywords": [
|
|
24
|
-
"
|
|
41
|
+
"express",
|
|
42
|
+
"versioning",
|
|
25
43
|
"api",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
"files": [
|
|
30
|
-
"dist"
|
|
44
|
+
"routes",
|
|
45
|
+
"middleware",
|
|
46
|
+
"semver"
|
|
31
47
|
],
|
|
32
48
|
"devDependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"@types/
|
|
35
|
-
"@types/
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
49
|
+
"@eslint/js": "^9.30.1",
|
|
50
|
+
"@types/express": "^5.0.3",
|
|
51
|
+
"@types/jest": "^30.0.0",
|
|
52
|
+
"@types/supertest": "^6.0.3",
|
|
53
|
+
"eslint": "^9.30.1",
|
|
54
|
+
"express": "5.1.0",
|
|
55
|
+
"jest": "^30.0.4",
|
|
56
|
+
"supertest": "^7.1.1",
|
|
57
|
+
"ts-jest": "^29.4.0",
|
|
58
|
+
"ts-node": "^10.9.2",
|
|
59
|
+
"typescript": "^5.8.3",
|
|
60
|
+
"typescript-eslint": "^8.35.1"
|
|
40
61
|
}
|
|
41
62
|
}
|
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const lib_1 = __importDefault(require("./lib"));
|
|
7
|
-
module.exports = lib_1.default;
|
|
8
|
-
exports.default = lib_1.default;
|
package/dist/lib.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interface for version handlers.
|
|
3
|
-
* Each key is a version string and the value is a function to handle requests for that version.
|
|
4
|
-
*/
|
|
5
|
-
interface VersionHandlers {
|
|
6
|
-
[key: string]: (req: any, res: any, next: any) => void;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Main middleware that handles version-based routing logic.
|
|
10
|
-
* @param {VersionHandlers} versionHandlers - The object containing all version handlers.
|
|
11
|
-
* @param {((req: any, res: any, next: any) => void) | undefined} defaultHandler - The default handler function.
|
|
12
|
-
* @returns {((req: any, res: any, next: any) => void)} - The middleware function.
|
|
13
|
-
*/
|
|
14
|
-
declare const versioningMiddleware: (versionHandlers: VersionHandlers, defaultHandler?: (req: any, res: any, next: any) => void) => ((req: any, res: any, next: any) => void);
|
|
15
|
-
export default versioningMiddleware;
|
package/dist/lib.js
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
/**
|
|
4
|
-
* Extracts the version from the request object.
|
|
5
|
-
* @param {any} req - The request object.
|
|
6
|
-
* @returns {string | boolean} - The version string if it exists, otherwise false.
|
|
7
|
-
*/
|
|
8
|
-
const extractVersionFromRequest = (req) => {
|
|
9
|
-
return req
|
|
10
|
-
? req.version
|
|
11
|
-
? String(req.version)
|
|
12
|
-
: req.headers["accept-version"]
|
|
13
|
-
: false;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Executes the corresponding version handler if there is a version match.
|
|
17
|
-
* @param {string} handlerKey - The key of the handler to execute.
|
|
18
|
-
* @param {string} clientVersion - The version from the client's request.
|
|
19
|
-
* @param {VersionHandlers} versionHandlers - The object containing all version handlers.
|
|
20
|
-
* @param {any} req - The request object.
|
|
21
|
-
* @param {any} res - The response object.
|
|
22
|
-
* @param {any} next - The next middleware function.
|
|
23
|
-
* @returns {boolean} - True if a matching version handler was executed, otherwise false.
|
|
24
|
-
*/
|
|
25
|
-
const executeVersionHandler = (handlerKey, clientVersion, versionHandlers, req, res, next) => {
|
|
26
|
-
let tempKey;
|
|
27
|
-
let versionArray = clientVersion.split(".");
|
|
28
|
-
let tempVersion;
|
|
29
|
-
if (handlerKey[0] === "~") {
|
|
30
|
-
// Handling versions using '~'
|
|
31
|
-
tempKey = handlerKey.substring(1).split(".").slice(0, 2).join(".");
|
|
32
|
-
versionArray[1] = versionArray[1] || "0";
|
|
33
|
-
tempVersion = versionArray.slice(0, 2).join(".");
|
|
34
|
-
}
|
|
35
|
-
else if (handlerKey[0] === "^") {
|
|
36
|
-
// Handling versions using '^'
|
|
37
|
-
tempKey = handlerKey.substring(1).split(".").slice(0, 1).join(".");
|
|
38
|
-
tempVersion = versionArray.slice(0, 1).join(".");
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
// Standard version handling
|
|
42
|
-
tempKey = handlerKey;
|
|
43
|
-
versionArray[1] = versionArray[1] || "0";
|
|
44
|
-
versionArray[2] = versionArray[2] || "0";
|
|
45
|
-
tempVersion = versionArray.join(".");
|
|
46
|
-
}
|
|
47
|
-
if (tempKey === tempVersion && versionHandlers[handlerKey]) {
|
|
48
|
-
// Call the corresponding version handler if there is a match
|
|
49
|
-
versionHandlers[handlerKey].call(this, req, res, next);
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
return false;
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* Executes the default handler if no matching version is found.
|
|
56
|
-
* @param {((req: any, res: any, next: any) => void) | undefined} defaultHandler - The default handler function.
|
|
57
|
-
* @param {VersionHandlers} versionHandlers - The object containing all version handlers.
|
|
58
|
-
* @param {string[]} keys - The keys of all version handlers.
|
|
59
|
-
* @param {any} req - The request object.
|
|
60
|
-
* @param {any} res - The response object.
|
|
61
|
-
* @param {any} next - The next middleware function.
|
|
62
|
-
*/
|
|
63
|
-
const executeDefaultHandler = (defaultHandler, versionHandlers, keys, req, res, next) => {
|
|
64
|
-
// Check if there is a defaultHandler defined and execute it if it exists
|
|
65
|
-
if (defaultHandler) {
|
|
66
|
-
defaultHandler(req, res, next);
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
// If there is no defaultHandler, determine the most recent version
|
|
70
|
-
const latestVersionKey = findLatestVersion(keys);
|
|
71
|
-
// If the latest version is found and there is a handler for that version, execute it
|
|
72
|
-
if (latestVersionKey && versionHandlers[latestVersionKey]) {
|
|
73
|
-
versionHandlers[latestVersionKey](req, res, next);
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
// If no valid version is found to handle the request, respond with a 422 error
|
|
77
|
-
res.status(422).send("Unprocessable Entity: No valid handler found for this request.");
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
/**
|
|
82
|
-
* Main middleware that handles version-based routing logic.
|
|
83
|
-
* @param {VersionHandlers} versionHandlers - The object containing all version handlers.
|
|
84
|
-
* @param {((req: any, res: any, next: any) => void) | undefined} defaultHandler - The default handler function.
|
|
85
|
-
* @returns {((req: any, res: any, next: any) => void)} - The middleware function.
|
|
86
|
-
*/
|
|
87
|
-
const versioningMiddleware = (versionHandlers, defaultHandler) => {
|
|
88
|
-
if (!versionHandlers ||
|
|
89
|
-
typeof versionHandlers !== "object" ||
|
|
90
|
-
Array.isArray(versionHandlers)) {
|
|
91
|
-
throw new Error("Invalid argument: 'versionHandlers' must be a non-array object.");
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
return (req, res, next) => {
|
|
95
|
-
const clientVersion = extractVersionFromRequest(req);
|
|
96
|
-
const keys = Object.keys(versionHandlers);
|
|
97
|
-
if (!clientVersion) {
|
|
98
|
-
// Execute the default handler if no version is provided
|
|
99
|
-
return res.status(422).send("Unprocessable Entity: No version provided.");
|
|
100
|
-
}
|
|
101
|
-
for (let i = 0; i < keys.length; i++) {
|
|
102
|
-
const handlerKey = keys[i];
|
|
103
|
-
if (executeVersionHandler(handlerKey, clientVersion, versionHandlers, req, res, next)) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// If no matching version is found, execute the default handler
|
|
108
|
-
executeDefaultHandler(defaultHandler, versionHandlers, keys, req, res, next);
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
/**
|
|
113
|
-
* Finds the latest version among an array of versions.
|
|
114
|
-
* @param {string[]} versions - The array of version strings.
|
|
115
|
-
* @returns {string | null} - The latest version string, or null if the array is empty.
|
|
116
|
-
*/
|
|
117
|
-
const findLatestVersion = (versions) => {
|
|
118
|
-
// Find the latest version among an array of versions
|
|
119
|
-
if (!versions || versions.length === 0) {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
const processedVersions = versions.map((version) => {
|
|
123
|
-
// Process versions by removing special characters and padding with zeros
|
|
124
|
-
const versionArr = version.replace(/[\^~]/g, "").split(".");
|
|
125
|
-
for (let i = 0; i < 2; i++) {
|
|
126
|
-
versionArr[i] = versionArr[i] || "0";
|
|
127
|
-
}
|
|
128
|
-
return versionArr;
|
|
129
|
-
});
|
|
130
|
-
// Sort processed versions to find the latest one
|
|
131
|
-
processedVersions.sort((v1Arr, v2Arr) => {
|
|
132
|
-
for (let i = 0; i < 2; i++) {
|
|
133
|
-
if (Number(v1Arr[i]) > Number(v2Arr[i])) {
|
|
134
|
-
return 1;
|
|
135
|
-
}
|
|
136
|
-
else if (Number(v1Arr[i]) < Number(v2Arr[i])) {
|
|
137
|
-
return -1;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return 0;
|
|
141
|
-
});
|
|
142
|
-
return processedVersions[processedVersions.length - 1].join(".");
|
|
143
|
-
};
|
|
144
|
-
// Exporta la función para CommonJS
|
|
145
|
-
module.exports = versioningMiddleware;
|
|
146
|
-
// Exporta la función como default para ES6 modules
|
|
147
|
-
exports.default = versioningMiddleware;
|