@sentienguard/apm 1.0.5 → 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/src/normalizer.js CHANGED
@@ -1,147 +1,147 @@
1
- /**
2
- * Route Normalizer
3
- * Normalizes dynamic routes before aggregation to prevent cardinality explosion.
4
- *
5
- * Examples:
6
- * /api/users/123 → /api/users/:id
7
- * /api/orders/ab23f9 → /api/orders/:id
8
- * /api/users/123/posts/456 → /api/users/:id/posts/:id
9
- */
10
-
11
- // Patterns for ID detection
12
- const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13
- const MONGO_ID_PATTERN = /^[0-9a-f]{24}$/i;
14
- const NUMERIC_PATTERN = /^\d+$/;
15
- const SHORT_HEX_PATTERN = /^[0-9a-f]{6,}$/i;
16
-
17
- // Max route length to prevent abuse
18
- const MAX_ROUTE_LENGTH = 200;
19
-
20
- /**
21
- * Check if a path segment looks like a dynamic ID
22
- */
23
- function isIdSegment(segment) {
24
- if (!segment) return false;
25
-
26
- // Check for common ID patterns
27
- if (NUMERIC_PATTERN.test(segment)) return true;
28
- if (UUID_PATTERN.test(segment)) return true;
29
- if (MONGO_ID_PATTERN.test(segment)) return true;
30
- if (SHORT_HEX_PATTERN.test(segment)) return true;
31
-
32
- return false;
33
- }
34
-
35
- /**
36
- * Normalize a route path by replacing dynamic segments with :id
37
- *
38
- * @param {string} path - The URL path to normalize
39
- * @returns {string} Normalized route path
40
- */
41
- export function normalizeRoute(path) {
42
- if (!path || typeof path !== 'string') {
43
- return '/unknown';
44
- }
45
-
46
- // Remove query string and hash
47
- let cleanPath = path.split('?')[0].split('#')[0];
48
-
49
- // Ensure path starts with /
50
- if (!cleanPath.startsWith('/')) {
51
- cleanPath = '/' + cleanPath;
52
- }
53
-
54
- // Truncate overly long paths
55
- if (cleanPath.length > MAX_ROUTE_LENGTH) {
56
- return '/unknown';
57
- }
58
-
59
- // Split into segments and normalize
60
- const segments = cleanPath.split('/');
61
- const normalizedSegments = segments.map(segment => {
62
- if (isIdSegment(segment)) {
63
- return ':id';
64
- }
65
- return segment;
66
- });
67
-
68
- const normalized = normalizedSegments.join('/');
69
-
70
- // Clean up multiple slashes
71
- return normalized.replace(/\/+/g, '/') || '/';
72
- }
73
-
74
- /**
75
- * Extract route pattern from Express/Fastify request
76
- * Prefers the framework's route pattern if available
77
- *
78
- * @param {object} req - HTTP request object
79
- * @returns {string} Route pattern
80
- */
81
- export function extractRoute(req) {
82
- // Express stores the matched route pattern
83
- if (req.route && req.route.path) {
84
- return req.baseUrl ? req.baseUrl + req.route.path : req.route.path;
85
- }
86
-
87
- // Fastify stores route info differently
88
- if (req.routerPath) {
89
- return req.routerPath;
90
- }
91
-
92
- // Fastify alternative
93
- if (req.routeOptions && req.routeOptions.url) {
94
- return req.routeOptions.url;
95
- }
96
-
97
- // Fall back to normalizing the URL
98
- return normalizeRoute(req.url || req.originalUrl || '/');
99
- }
100
-
101
- /**
102
- * Route registry to track and limit unique routes
103
- */
104
- export class RouteRegistry {
105
- constructor(maxRoutes = 100) {
106
- this.maxRoutes = maxRoutes;
107
- this.routes = new Set();
108
- }
109
-
110
- /**
111
- * Register a route and check if it should be tracked
112
- * Returns the route if under limit, '/unknown' if limit exceeded
113
- */
114
- register(route) {
115
- if (this.routes.has(route)) {
116
- return route;
117
- }
118
-
119
- if (this.routes.size >= this.maxRoutes) {
120
- // Bucket overflow routes as /unknown
121
- return '/unknown';
122
- }
123
-
124
- this.routes.add(route);
125
- return route;
126
- }
127
-
128
- /**
129
- * Get count of unique routes
130
- */
131
- get size() {
132
- return this.routes.size;
133
- }
134
-
135
- /**
136
- * Reset the registry
137
- */
138
- clear() {
139
- this.routes.clear();
140
- }
141
- }
142
-
143
- export default {
144
- normalizeRoute,
145
- extractRoute,
146
- RouteRegistry
147
- };
1
+ /**
2
+ * Route Normalizer
3
+ * Normalizes dynamic routes before aggregation to prevent cardinality explosion.
4
+ *
5
+ * Examples:
6
+ * /api/users/123 → /api/users/:id
7
+ * /api/orders/ab23f9 → /api/orders/:id
8
+ * /api/users/123/posts/456 → /api/users/:id/posts/:id
9
+ */
10
+
11
+ // Patterns for ID detection
12
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13
+ const MONGO_ID_PATTERN = /^[0-9a-f]{24}$/i;
14
+ const NUMERIC_PATTERN = /^\d+$/;
15
+ const SHORT_HEX_PATTERN = /^[0-9a-f]{6,}$/i;
16
+
17
+ // Max route length to prevent abuse
18
+ const MAX_ROUTE_LENGTH = 200;
19
+
20
+ /**
21
+ * Check if a path segment looks like a dynamic ID
22
+ */
23
+ function isIdSegment(segment) {
24
+ if (!segment) return false;
25
+
26
+ // Check for common ID patterns
27
+ return (
28
+ NUMERIC_PATTERN.test(segment) ||
29
+ UUID_PATTERN.test(segment) ||
30
+ MONGO_ID_PATTERN.test(segment) ||
31
+ SHORT_HEX_PATTERN.test(segment)
32
+ );
33
+ }
34
+
35
+ /**
36
+ * Normalize a route path by replacing dynamic segments with :id
37
+ *
38
+ * @param {string} path - The URL path to normalize
39
+ * @returns {string} Normalized route path
40
+ */
41
+ export function normalizeRoute(path) {
42
+ if (!path || typeof path !== 'string') {
43
+ return '/unknown';
44
+ }
45
+
46
+ // Remove query string and hash
47
+ let cleanPath = path.split('?')[0].split('#')[0];
48
+
49
+ // Ensure path starts with /
50
+ if (!cleanPath.startsWith('/')) {
51
+ cleanPath = '/' + cleanPath;
52
+ }
53
+
54
+ // Truncate overly long paths
55
+ if (cleanPath.length > MAX_ROUTE_LENGTH) {
56
+ return '/unknown';
57
+ }
58
+
59
+ // Split into segments and normalize
60
+ const segments = cleanPath.split('/');
61
+ const normalizedSegments = segments.map(segment => {
62
+ if (isIdSegment(segment)) {
63
+ return ':id';
64
+ }
65
+ return segment;
66
+ });
67
+
68
+ const normalized = normalizedSegments.join('/');
69
+
70
+ // Clean up multiple slashes
71
+ return normalized.replace(/\/+/g, '/') || '/';
72
+ }
73
+
74
+ /**
75
+ * Extract route pattern from Express/Fastify request
76
+ * Prefers the framework's route pattern if available
77
+ *
78
+ * @param {object} req - HTTP request object
79
+ * @returns {string} Route pattern
80
+ */
81
+ export function extractRoute(req) {
82
+ // Express stores the matched route pattern
83
+ if (req.route?.path) {
84
+ return req.baseUrl ? req.baseUrl + req.route.path : req.route.path;
85
+ }
86
+
87
+ // Fastify stores route info differently
88
+ if (req.routerPath) {
89
+ return req.routerPath;
90
+ }
91
+
92
+ // Fastify alternative
93
+ if (req.routeOptions?.url) {
94
+ return req.routeOptions.url;
95
+ }
96
+
97
+ // Fall back to normalizing the URL
98
+ return normalizeRoute(req.url || req.originalUrl || '/');
99
+ }
100
+
101
+ /**
102
+ * Route registry to track and limit unique routes
103
+ */
104
+ export class RouteRegistry {
105
+ constructor(maxRoutes = 100) {
106
+ this.maxRoutes = maxRoutes;
107
+ this.routes = new Set();
108
+ }
109
+
110
+ /**
111
+ * Register a route and check if it should be tracked
112
+ * Returns the route if under limit, '/unknown' if limit exceeded
113
+ */
114
+ register(route) {
115
+ if (this.routes.has(route)) {
116
+ return route;
117
+ }
118
+
119
+ if (this.routes.size >= this.maxRoutes) {
120
+ // Bucket overflow routes as /unknown
121
+ return '/unknown';
122
+ }
123
+
124
+ this.routes.add(route);
125
+ return route;
126
+ }
127
+
128
+ /**
129
+ * Get count of unique routes
130
+ */
131
+ get size() {
132
+ return this.routes.size;
133
+ }
134
+
135
+ /**
136
+ * Reset the registry
137
+ */
138
+ clear() {
139
+ this.routes.clear();
140
+ }
141
+ }
142
+
143
+ export default {
144
+ normalizeRoute,
145
+ extractRoute,
146
+ RouteRegistry
147
+ };