navis.js 4.0.0 → 5.2.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 +37 -2
- package/examples/v5-features-demo.js +167 -0
- package/examples/v5.1-features-demo.js +192 -0
- package/examples/v5.2-features-demo.js +153 -0
- package/package.json +1 -1
- package/src/cache/cache.js +157 -0
- package/src/cache/redis-cache.js +174 -0
- package/src/core/app.js +9 -0
- package/src/core/graceful-shutdown.js +77 -0
- package/src/core/versioning.js +124 -0
- package/src/db/db-pool.js +195 -0
- package/src/docs/swagger.js +188 -0
- package/src/health/health-checker.js +120 -0
- package/src/index.js +51 -0
- package/src/middleware/cache-middleware.js +105 -0
- package/src/middleware/compression.js +97 -0
- package/src/middleware/cors.js +86 -0
- package/src/middleware/security.js +107 -0
- package/src/middleware/upload.js +151 -0
- package/src/sse/server-sent-events.js +171 -0
- package/src/testing/test-helper.js +167 -0
- package/src/websocket/websocket-server.js +301 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Cache
|
|
3
|
+
* v5: Simple in-memory caching with TTL support
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class Cache {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.store = new Map();
|
|
9
|
+
this.maxSize = options.maxSize || 1000;
|
|
10
|
+
this.defaultTTL = options.defaultTTL || 3600000; // 1 hour in ms
|
|
11
|
+
this.cleanupInterval = options.cleanupInterval || 60000; // 1 minute
|
|
12
|
+
|
|
13
|
+
// Start cleanup interval
|
|
14
|
+
this.intervalId = setInterval(() => {
|
|
15
|
+
this._cleanup();
|
|
16
|
+
}, this.cleanupInterval);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Set a value in cache
|
|
21
|
+
* @param {string} key - Cache key
|
|
22
|
+
* @param {*} value - Value to cache
|
|
23
|
+
* @param {number} ttl - Time to live in milliseconds
|
|
24
|
+
*/
|
|
25
|
+
set(key, value, ttl = null) {
|
|
26
|
+
const expiration = Date.now() + (ttl || this.defaultTTL);
|
|
27
|
+
|
|
28
|
+
// Remove oldest entries if at max size
|
|
29
|
+
if (this.store.size >= this.maxSize && !this.store.has(key)) {
|
|
30
|
+
this._evictOldest();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.store.set(key, {
|
|
34
|
+
value,
|
|
35
|
+
expiration,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get a value from cache
|
|
41
|
+
* @param {string} key - Cache key
|
|
42
|
+
* @returns {*} - Cached value or null
|
|
43
|
+
*/
|
|
44
|
+
get(key) {
|
|
45
|
+
const entry = this.store.get(key);
|
|
46
|
+
|
|
47
|
+
if (!entry) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check if expired
|
|
52
|
+
if (Date.now() > entry.expiration) {
|
|
53
|
+
this.store.delete(key);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return entry.value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if key exists in cache
|
|
62
|
+
* @param {string} key - Cache key
|
|
63
|
+
* @returns {boolean} - True if key exists and not expired
|
|
64
|
+
*/
|
|
65
|
+
has(key) {
|
|
66
|
+
const entry = this.store.get(key);
|
|
67
|
+
|
|
68
|
+
if (!entry) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (Date.now() > entry.expiration) {
|
|
73
|
+
this.store.delete(key);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Delete a key from cache
|
|
82
|
+
* @param {string} key - Cache key
|
|
83
|
+
* @returns {boolean} - True if key was deleted
|
|
84
|
+
*/
|
|
85
|
+
delete(key) {
|
|
86
|
+
return this.store.delete(key);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Clear all cache entries
|
|
91
|
+
*/
|
|
92
|
+
clear() {
|
|
93
|
+
this.store.clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get cache size
|
|
98
|
+
* @returns {number} - Number of entries
|
|
99
|
+
*/
|
|
100
|
+
size() {
|
|
101
|
+
return this.store.size;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get all keys
|
|
106
|
+
* @returns {Array} - Array of cache keys
|
|
107
|
+
*/
|
|
108
|
+
keys() {
|
|
109
|
+
return Array.from(this.store.keys());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Cleanup expired entries
|
|
114
|
+
* @private
|
|
115
|
+
*/
|
|
116
|
+
_cleanup() {
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
for (const [key, entry] of this.store.entries()) {
|
|
119
|
+
if (now > entry.expiration) {
|
|
120
|
+
this.store.delete(key);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Evict oldest entry (LRU-like)
|
|
127
|
+
* @private
|
|
128
|
+
*/
|
|
129
|
+
_evictOldest() {
|
|
130
|
+
let oldestKey = null;
|
|
131
|
+
let oldestExpiration = Infinity;
|
|
132
|
+
|
|
133
|
+
for (const [key, entry] of this.store.entries()) {
|
|
134
|
+
if (entry.expiration < oldestExpiration) {
|
|
135
|
+
oldestExpiration = entry.expiration;
|
|
136
|
+
oldestKey = key;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (oldestKey) {
|
|
141
|
+
this.store.delete(oldestKey);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Destroy cache (cleanup)
|
|
147
|
+
*/
|
|
148
|
+
destroy() {
|
|
149
|
+
if (this.intervalId) {
|
|
150
|
+
clearInterval(this.intervalId);
|
|
151
|
+
}
|
|
152
|
+
this.clear();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = Cache;
|
|
157
|
+
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis Cache Adapter
|
|
3
|
+
* v5: Redis-based caching (requires redis package)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class RedisCache {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.client = null;
|
|
9
|
+
this.defaultTTL = options.defaultTTL || 3600; // 1 hour in seconds
|
|
10
|
+
this.prefix = options.prefix || 'navis:';
|
|
11
|
+
this.connected = false;
|
|
12
|
+
|
|
13
|
+
// Try to require redis (optional dependency)
|
|
14
|
+
try {
|
|
15
|
+
const redis = require('redis');
|
|
16
|
+
this.redis = redis;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new Error('Redis package not installed. Install with: npm install redis');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Connect to Redis
|
|
24
|
+
* @param {Object} options - Redis connection options
|
|
25
|
+
*/
|
|
26
|
+
async connect(options = {}) {
|
|
27
|
+
if (this.connected && this.client) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
url = process.env.REDIS_URL || 'redis://localhost:6379',
|
|
33
|
+
socket = {},
|
|
34
|
+
...otherOptions
|
|
35
|
+
} = options;
|
|
36
|
+
|
|
37
|
+
this.client = this.redis.createClient({
|
|
38
|
+
url,
|
|
39
|
+
socket: {
|
|
40
|
+
reconnectStrategy: (retries) => {
|
|
41
|
+
if (retries > 10) {
|
|
42
|
+
return new Error('Too many reconnection attempts');
|
|
43
|
+
}
|
|
44
|
+
return Math.min(retries * 100, 3000);
|
|
45
|
+
},
|
|
46
|
+
...socket,
|
|
47
|
+
},
|
|
48
|
+
...otherOptions,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.client.on('error', (err) => {
|
|
52
|
+
console.error('Redis Client Error:', err);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await this.client.connect();
|
|
56
|
+
this.connected = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Disconnect from Redis
|
|
61
|
+
*/
|
|
62
|
+
async disconnect() {
|
|
63
|
+
if (this.client && this.connected) {
|
|
64
|
+
await this.client.quit();
|
|
65
|
+
this.connected = false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Set a value in cache
|
|
71
|
+
* @param {string} key - Cache key
|
|
72
|
+
* @param {*} value - Value to cache
|
|
73
|
+
* @param {number} ttl - Time to live in seconds
|
|
74
|
+
*/
|
|
75
|
+
async set(key, value, ttl = null) {
|
|
76
|
+
if (!this.connected) {
|
|
77
|
+
throw new Error('Redis not connected. Call connect() first.');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const fullKey = this.prefix + key;
|
|
81
|
+
const serialized = JSON.stringify(value);
|
|
82
|
+
const expiration = ttl || this.defaultTTL;
|
|
83
|
+
|
|
84
|
+
if (expiration > 0) {
|
|
85
|
+
await this.client.setEx(fullKey, expiration, serialized);
|
|
86
|
+
} else {
|
|
87
|
+
await this.client.set(fullKey, serialized);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get a value from cache
|
|
93
|
+
* @param {string} key - Cache key
|
|
94
|
+
* @returns {*} - Cached value or null
|
|
95
|
+
*/
|
|
96
|
+
async get(key) {
|
|
97
|
+
if (!this.connected) {
|
|
98
|
+
throw new Error('Redis not connected. Call connect() first.');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const fullKey = this.prefix + key;
|
|
102
|
+
const serialized = await this.client.get(fullKey);
|
|
103
|
+
|
|
104
|
+
if (!serialized) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(serialized);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Check if key exists in cache
|
|
117
|
+
* @param {string} key - Cache key
|
|
118
|
+
* @returns {boolean} - True if key exists
|
|
119
|
+
*/
|
|
120
|
+
async has(key) {
|
|
121
|
+
if (!this.connected) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const fullKey = this.prefix + key;
|
|
126
|
+
const exists = await this.client.exists(fullKey);
|
|
127
|
+
return exists === 1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Delete a key from cache
|
|
132
|
+
* @param {string} key - Cache key
|
|
133
|
+
* @returns {boolean} - True if key was deleted
|
|
134
|
+
*/
|
|
135
|
+
async delete(key) {
|
|
136
|
+
if (!this.connected) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const fullKey = this.prefix + key;
|
|
141
|
+
const result = await this.client.del(fullKey);
|
|
142
|
+
return result > 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Clear all cache entries with prefix
|
|
147
|
+
*/
|
|
148
|
+
async clear() {
|
|
149
|
+
if (!this.connected) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const keys = await this.client.keys(this.prefix + '*');
|
|
154
|
+
if (keys.length > 0) {
|
|
155
|
+
await this.client.del(keys);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get cache size (approximate)
|
|
161
|
+
* @returns {number} - Number of keys with prefix
|
|
162
|
+
*/
|
|
163
|
+
async size() {
|
|
164
|
+
if (!this.connected) {
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const keys = await this.client.keys(this.prefix + '*');
|
|
169
|
+
return keys.length;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = RedisCache;
|
|
174
|
+
|
package/src/core/app.js
CHANGED
|
@@ -238,6 +238,7 @@ class NavisApp {
|
|
|
238
238
|
* Start HTTP server (Node.js)
|
|
239
239
|
* @param {number} port - Port number
|
|
240
240
|
* @param {Function} callback - Optional callback
|
|
241
|
+
* @returns {Object} - HTTP server instance
|
|
241
242
|
*/
|
|
242
243
|
listen(port = 3000, callback) {
|
|
243
244
|
this.server = http.createServer((req, res) => {
|
|
@@ -251,6 +252,14 @@ class NavisApp {
|
|
|
251
252
|
|
|
252
253
|
return this.server;
|
|
253
254
|
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get server instance
|
|
258
|
+
* @returns {Object|null} - HTTP server instance
|
|
259
|
+
*/
|
|
260
|
+
getServer() {
|
|
261
|
+
return this.server;
|
|
262
|
+
}
|
|
254
263
|
}
|
|
255
264
|
|
|
256
265
|
module.exports = NavisApp;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graceful Shutdown Handler
|
|
3
|
+
* v5: Clean shutdown handling for Node.js servers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Graceful shutdown handler
|
|
8
|
+
* @param {Object} server - HTTP server instance
|
|
9
|
+
* @param {Object} options - Shutdown options
|
|
10
|
+
*/
|
|
11
|
+
function gracefulShutdown(server, options = {}) {
|
|
12
|
+
const {
|
|
13
|
+
timeout = 10000, // 10 seconds default
|
|
14
|
+
onShutdown = async () => {}, // Cleanup function
|
|
15
|
+
signals = ['SIGTERM', 'SIGINT'],
|
|
16
|
+
log = console.log,
|
|
17
|
+
} = options;
|
|
18
|
+
|
|
19
|
+
let isShuttingDown = false;
|
|
20
|
+
|
|
21
|
+
const shutdown = async (signal) => {
|
|
22
|
+
if (isShuttingDown) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
isShuttingDown = true;
|
|
27
|
+
log(`Received ${signal}, starting graceful shutdown...`);
|
|
28
|
+
|
|
29
|
+
// Stop accepting new connections
|
|
30
|
+
server.close(() => {
|
|
31
|
+
log('HTTP server closed');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Set timeout for forced shutdown
|
|
35
|
+
const shutdownTimer = setTimeout(() => {
|
|
36
|
+
log('Forced shutdown after timeout');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}, timeout);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
// Run cleanup
|
|
42
|
+
await onShutdown();
|
|
43
|
+
clearTimeout(shutdownTimer);
|
|
44
|
+
log('Graceful shutdown completed');
|
|
45
|
+
process.exit(0);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
clearTimeout(shutdownTimer);
|
|
48
|
+
log('Error during shutdown:', error);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Register signal handlers
|
|
54
|
+
for (const signal of signals) {
|
|
55
|
+
process.on(signal, () => shutdown(signal));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Handle uncaught exceptions
|
|
59
|
+
process.on('uncaughtException', async (error) => {
|
|
60
|
+
log('Uncaught exception:', error);
|
|
61
|
+
await shutdown('uncaughtException');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Handle unhandled promise rejections
|
|
65
|
+
process.on('unhandledRejection', async (reason, promise) => {
|
|
66
|
+
log('Unhandled rejection:', reason);
|
|
67
|
+
await shutdown('unhandledRejection');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
shutdown: () => shutdown('manual'),
|
|
72
|
+
isShuttingDown: () => isShuttingDown,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = gracefulShutdown;
|
|
77
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Versioning
|
|
3
|
+
* v5.1: URL-based and header-based API versioning
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class VersionManager {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.versions = new Map();
|
|
9
|
+
this.defaultVersion = null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a version router
|
|
14
|
+
* @param {string} version - Version identifier (e.g., 'v1', 'v2')
|
|
15
|
+
* @returns {Object} - Version router with route methods
|
|
16
|
+
*/
|
|
17
|
+
version(version) {
|
|
18
|
+
if (!this.versions.has(version)) {
|
|
19
|
+
const router = {
|
|
20
|
+
version,
|
|
21
|
+
routes: {
|
|
22
|
+
GET: [],
|
|
23
|
+
POST: [],
|
|
24
|
+
PUT: [],
|
|
25
|
+
DELETE: [],
|
|
26
|
+
PATCH: [],
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
this.versions.set(version, router);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const router = this.versions.get(version);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
get: (path, handler) => this._register(router, 'GET', path, handler),
|
|
36
|
+
post: (path, handler) => this._register(router, 'POST', path, handler),
|
|
37
|
+
put: (path, handler) => this._register(router, 'PUT', path, handler),
|
|
38
|
+
delete: (path, handler) => this._register(router, 'DELETE', path, handler),
|
|
39
|
+
patch: (path, handler) => this._register(router, 'PATCH', path, handler),
|
|
40
|
+
use: (middleware) => {
|
|
41
|
+
if (!router.middlewares) {
|
|
42
|
+
router.middlewares = [];
|
|
43
|
+
}
|
|
44
|
+
router.middlewares.push(middleware);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Register a route for a version
|
|
51
|
+
* @private
|
|
52
|
+
*/
|
|
53
|
+
_register(router, method, path, handler) {
|
|
54
|
+
const versionedPath = `/${router.version}${path}`;
|
|
55
|
+
router.routes[method].push({
|
|
56
|
+
path: versionedPath,
|
|
57
|
+
originalPath: path,
|
|
58
|
+
handler,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Set default version
|
|
64
|
+
* @param {string} version - Default version
|
|
65
|
+
*/
|
|
66
|
+
setDefaultVersion(version) {
|
|
67
|
+
this.defaultVersion = version;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get routes for a version
|
|
72
|
+
* @param {string} version - Version identifier
|
|
73
|
+
* @returns {Object} - Routes for the version
|
|
74
|
+
*/
|
|
75
|
+
getRoutes(version) {
|
|
76
|
+
const router = this.versions.get(version);
|
|
77
|
+
return router ? router.routes : null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get all versions
|
|
82
|
+
* @returns {Array} - Array of version identifiers
|
|
83
|
+
*/
|
|
84
|
+
getVersions() {
|
|
85
|
+
return Array.from(this.versions.keys());
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create version manager
|
|
91
|
+
* @returns {VersionManager} - Version manager instance
|
|
92
|
+
*/
|
|
93
|
+
function createVersionManager() {
|
|
94
|
+
return new VersionManager();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Header-based versioning middleware
|
|
99
|
+
* @param {Object} options - Versioning options
|
|
100
|
+
* @returns {Function} - Middleware function
|
|
101
|
+
*/
|
|
102
|
+
function headerVersioning(options = {}) {
|
|
103
|
+
const {
|
|
104
|
+
header = 'X-API-Version',
|
|
105
|
+
defaultVersion = null,
|
|
106
|
+
} = options;
|
|
107
|
+
|
|
108
|
+
return async (req, res, next) => {
|
|
109
|
+
const version = req.headers[header] || req.headers[header.toLowerCase()] || defaultVersion;
|
|
110
|
+
|
|
111
|
+
if (version) {
|
|
112
|
+
req.apiVersion = version;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
next();
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = {
|
|
120
|
+
VersionManager,
|
|
121
|
+
createVersionManager,
|
|
122
|
+
headerVersioning,
|
|
123
|
+
};
|
|
124
|
+
|