@usageflow/express 0.1.9 → 0.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 CHANGED
@@ -1,8 +1,9 @@
1
1
  # @usageflow/express
2
2
 
3
- Express.js middleware for UsageFlow API tracking. Easily monitor and analyze your Express.js API usage.
3
+ Express.js middleware for UsageFlow API tracking. Easily monitor and analyze your Express.js API usage with real-time tracking and allocation management.
4
4
 
5
- ⚠️ **Beta Version Notice**: This package is currently in beta. Early adopters may encounter issues. We appreciate your feedback and contributions!
5
+ [![npm version](https://img.shields.io/npm/v/@usageflow/express.svg)](https://www.npmjs.com/package/@usageflow/express)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
7
 
7
8
  ## Installation
8
9
 
@@ -13,78 +14,234 @@ npm install @usageflow/express
13
14
  ## Quick Start
14
15
 
15
16
  ```javascript
16
- const express = require("express");
17
- const { ExpressUsageFlowAPI } = require("@usageflow/express");
17
+ const express = require('express');
18
+ const { ExpressUsageFlowAPI } = require('@usageflow/express');
18
19
 
19
20
  const app = express();
20
21
  app.use(express.json());
21
22
 
22
- // Initialize UsageFlow
23
- const usageFlow = new ExpressUsageFlowAPI();
24
- usageFlow.init("YOUR_API_KEY");
23
+ // Initialize UsageFlow with API key and optional pool size
24
+ const usageFlow = new ExpressUsageFlowAPI({
25
+ apiKey: 'YOUR_API_KEY',
26
+ poolSize: 5, // Optional: Number of WebSocket connections (default: 5)
27
+ });
25
28
 
26
29
  // Create middleware
27
30
  const middleware = usageFlow.createMiddleware(
28
31
  [
29
- { method: "*", url: "*" }, // Track all routes
32
+ { method: '*', url: '*' }, // Track all routes
30
33
  ],
31
34
  [
32
- { method: "GET", url: "/api/health" }, // Whitelist health check
33
- ],
35
+ { method: 'GET', url: '/api/health' }, // Whitelist health check
36
+ ]
34
37
  );
35
38
 
36
39
  // Apply middleware
37
40
  app.use(middleware);
38
41
 
39
42
  // Your routes
40
- app.get("/api/users", (req, res) => {
41
- res.json({ users: ["John", "Jane"] });
43
+ app.get('/api/users', (req, res) => {
44
+ res.json({ users: ['John', 'Jane'] });
42
45
  });
43
46
 
44
47
  app.listen(3000, () => {
45
- console.log("Server running on port 3000");
48
+ console.log('Server running on port 3000');
46
49
  });
47
50
  ```
48
51
 
49
52
  ## TypeScript Support
50
53
 
51
54
  ```typescript
52
- import express from "express";
53
- import { ExpressUsageFlowAPI } from "@usageflow/express";
55
+ import express from 'express';
56
+ import { ExpressUsageFlowAPI } from '@usageflow/express';
54
57
 
55
58
  const app = express();
56
59
  app.use(express.json());
57
60
 
58
- const usageFlow = new ExpressUsageFlowAPI();
59
- usageFlow.init("YOUR_API_KEY");
61
+ // Initialize UsageFlow with API key and optional pool size
62
+ const usageFlow = new ExpressUsageFlowAPI({
63
+ apiKey: 'YOUR_API_KEY',
64
+ poolSize: 5, // Optional: Number of WebSocket connections (default: 5)
65
+ });
66
+
67
+ // Create middleware
68
+ const middleware = usageFlow.createMiddleware(
69
+ [
70
+ { method: '*', url: '*' }, // Track all routes
71
+ ],
72
+ [
73
+ { method: 'GET', url: '/api/health' }, // Whitelist health check
74
+ ]
75
+ );
60
76
 
61
- // Rest of the code remains the same
77
+ // Apply middleware
78
+ app.use(middleware);
79
+
80
+ // Your routes
81
+ app.get('/api/users', (req, res) => {
82
+ res.json({ users: ['John', 'Jane'] });
83
+ });
84
+
85
+ app.listen(3000, () => {
86
+ console.log('Server running on port 3000');
87
+ });
62
88
  ```
63
89
 
64
- ## Configuration Options
90
+ ## Configuration
91
+
92
+ ### ExpressUsageFlowAPI
93
+
94
+ #### Constructor Options
95
+
96
+ ```typescript
97
+ interface UsageFlowAPIConfig {
98
+ apiKey: string; // Your UsageFlow API key (required)
99
+ poolSize?: number; // Number of WebSocket connections (default: 5)
100
+ }
101
+ ```
102
+
103
+ ### createMiddleware
104
+
105
+ Creates Express middleware for tracking API usage.
106
+
107
+ ```typescript
108
+ createMiddleware(
109
+ routes: Route[], // Routes to track
110
+ whitelistRoutes?: Route[] // Routes to exclude from tracking
111
+ ): (req: Request, res: Response, next: NextFunction) => Promise<void>
112
+ ```
113
+
114
+ #### Route Configuration
65
115
 
66
116
  ```typescript
67
117
  interface Route {
68
- method: string; // HTTP method or '*' for all methods
69
- url: string; // URL pattern or '*' for all URLs
118
+ method: string; // HTTP method ('GET', 'POST', 'PUT', 'DELETE', etc.) or '*' for all methods
119
+ url: string; // URL pattern or '*' for all URLs
70
120
  }
121
+ ```
122
+
123
+ #### Examples
124
+
125
+ **Track all routes:**
126
+
127
+ ```typescript
128
+ const middleware = usageFlow.createMiddleware([{ method: '*', url: '*' }]);
129
+ ```
130
+
131
+ **Track specific routes:**
71
132
 
72
- usageFlow.createMiddleware(
73
- routes: Route[], // Routes to track
74
- whitelistRoutes?: Route[] // Routes to exclude from tracking
133
+ ```typescript
134
+ const middleware = usageFlow.createMiddleware([
135
+ { method: 'GET', url: '/api/users' },
136
+ { method: 'POST', url: '/api/users' },
137
+ { method: 'PUT', url: '/api/users/:id' },
138
+ ]);
139
+ ```
140
+
141
+ **Track with whitelist:**
142
+
143
+ ```typescript
144
+ const middleware = usageFlow.createMiddleware(
145
+ [
146
+ { method: '*', url: '*' }, // Track all routes
147
+ ],
148
+ [
149
+ { method: 'GET', url: '/api/health' }, // Exclude health check
150
+ { method: 'GET', url: '/api/metrics' }, // Exclude metrics
151
+ ]
75
152
  );
76
153
  ```
77
154
 
78
- ## Beta Status
155
+ ## Features
156
+
157
+ - **Automatic Route Detection**: Automatically detects route patterns from Express router
158
+ - **Request Metadata Collection**: Collects comprehensive request metadata including headers, query params, path params, and body
159
+ - **Response Tracking**: Tracks response status codes and duration
160
+ - **WebSocket Communication**: Uses WebSocket for real-time communication with UsageFlow API
161
+ - **Connection Pooling**: Maintains a pool of WebSocket connections for better performance
162
+ - **Header Sanitization**: Automatically sanitizes sensitive headers (authorization, API keys)
163
+ - **Error Handling**: Gracefully handles errors and provides meaningful error messages
164
+
165
+ ## Request Metadata
166
+
167
+ The middleware automatically collects the following metadata for each request:
168
+
169
+ - HTTP method
170
+ - Route pattern
171
+ - Raw URL
172
+ - Client IP (with X-Forwarded-For support)
173
+ - User agent
174
+ - Timestamp
175
+ - Headers (sanitized)
176
+ - Query parameters
177
+ - Path parameters
178
+ - Request body
179
+ - Response status code
180
+ - Request duration
181
+
182
+ ## Error Handling
183
+
184
+ If an allocation request fails, the middleware will:
185
+
186
+ 1. Return a 400 status code
187
+ 2. Include an error message in the response
188
+ 3. Set `blocked: true` in the response body
189
+
190
+ ```typescript
191
+ // Error response format
192
+ {
193
+ message: "Error message",
194
+ blocked: true
195
+ }
196
+ ```
197
+
198
+ ## Advanced Usage
199
+
200
+ ### Custom Route Pattern Extraction
201
+
202
+ The middleware automatically extracts route patterns from Express. It supports:
203
+
204
+ - Direct route paths (`req.route.path`)
205
+ - Router stack traversal
206
+ - Nested routers
207
+ - Parameterized routes (`/users/:id`)
208
+
209
+ ### Ledger ID Generation
210
+
211
+ The middleware automatically generates ledger IDs based on:
212
+
213
+ 1. HTTP method and route pattern
214
+ 2. Configured identity fields from UsageFlow policies
215
+ 3. Identity field locations (path params, query params, body, bearer token, headers)
79
216
 
80
- This is a beta release meant for early adopters. You may encounter:
217
+ ## Requirements
81
218
 
82
- - API changes in future versions
83
- - Incomplete features
84
- - Potential bugs
219
+ - Node.js >= 18.0.0
220
+ - Express >= 4.17.0
221
+ - TypeScript >= 5.0.0 (for TypeScript projects)
85
222
 
86
- We're actively working on improvements and appreciate your feedback!
223
+ ## Dependencies
224
+
225
+ - `@usageflow/core`: Core UsageFlow functionality
226
+ - `express`: Express.js framework
227
+
228
+ ## Development
229
+
230
+ ```bash
231
+ # Install dependencies
232
+ npm install
233
+
234
+ # Build the package
235
+ npm run build
236
+
237
+ # Run tests
238
+ npm test
239
+ ```
87
240
 
88
241
  ## License
89
242
 
90
243
  MIT
244
+
245
+ ## Support
246
+
247
+ For issues, questions, or contributions, please visit our [GitHub repository](https://github.com/usageflow/js-mongorepo).
package/dist/plugin.js CHANGED
@@ -44,7 +44,7 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
44
44
  };
45
45
  return metadata;
46
46
  }
47
- async executeRequestWithMetadata(ledgerId, metadata, request, response) {
47
+ async executeRequestWithMetadata(ledgerId, metadata, request, response, hasLimit) {
48
48
  if (!this.apiKey) {
49
49
  throw new Error("API key not initialized");
50
50
  }
@@ -54,7 +54,7 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
54
54
  metadata,
55
55
  duration: 1000
56
56
  };
57
- await this.allocationRequest(request, payload, metadata);
57
+ await this.allocationRequest(request, payload, metadata, hasLimit);
58
58
  }
59
59
  createMiddleware(routes, whitelistRoutes = []) {
60
60
  const routesMap = this.createRoutesMap(routes);
@@ -75,9 +75,12 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
75
75
  }
76
76
  const metadata = await this.collectRequestMetadata(request);
77
77
  metadata.url = url;
78
- let ledgerId = this.guessLedgerId(request);
78
+ const { ledgerId, hasLimit } = this.guessLedgerId(request);
79
79
  try {
80
- await this.executeRequestWithMetadata(ledgerId, metadata, request, response);
80
+ if (this.blockedEndpoints.includes(ledgerId)) {
81
+ throw new core_1.UsageFlowError(`Endpoint is been blocked by the administrator`);
82
+ }
83
+ await this.executeRequestWithMetadata(ledgerId, metadata, request, response, hasLimit);
81
84
  // Capture response data
82
85
  const originalEnd = response.end;
83
86
  response.end = function (chunk, encoding, cb) {
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";;;AACA,0CAAmJ;AAInJ,MAAa,mBAAoB,SAAQ,mBAAY;IACjD,YAAY,OAA2B;QACnC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;IACD;;;;OAIG;IAEK,KAAK,CAAC,sBAAsB,CAChC,OAAgB;QAEhB,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAChC,OAAO,CAAC,OAAiC,CAC5C,CAAC;QAEF,4CAA4C;QAC5C,IAAI,QAAQ,GAAG,OAAO,CAAC,EAAE,CAAC;QAC1B,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACxD,IAAI,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YACnD,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACjD,CAAC;QAED,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,EAAE,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAU,EAAE,EAAE;YACtF,6BAA6B;YAC7B,IAAI,CAAC,KAAK,CAAC,KAAK;gBAAE,OAAO,KAAK,CAAC;YAC/B,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBACb,OAAO,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC;YACrC,CAAC;YAED,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACf,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC1C,CAAC;QAEL,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC;QAE/B,MAAM,QAAQ,GAAoB;YAC9B,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,GAAG,EAAE,YAAY;YACjB,MAAM,EAAE,OAAO,CAAC,WAAW,IAAI,GAAG;YAClC,QAAQ,EAAE,QAAQ,IAAI,SAAS;YAC/B,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,YAAY,CAAW;YAClD,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,OAAO;YACP,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;YACzE,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI;YAC1E,IAAI,EAAE,OAAO,CAAC,IAAI;SACrB,CAAC;QAEF,OAAO,QAAQ,CAAC;IACpB,CAAC;IAEO,KAAK,CAAC,0BAA0B,CACpC,QAAgB,EAChB,QAAyB,EACzB,OAAgB,EAChB,QAAkB,EAClB,QAAiB;QAEjB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,OAAO,GAAG;YACZ,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,CAAC;YACT,QAAQ;YACR,QAAQ,EAAE,IAAI;SACjB,CAAC;QAEF,MAAM,IAAI,CAAC,iBAAiB,CAAC,OAAsC,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACtG,CAAC;IAEM,gBAAgB,CAAC,MAAe,EAAE,kBAA2B,EAAE;QAClE,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC;QAGlB,OAAO,KAAK,EAAE,OAAgB,EAAE,QAAkB,EAAE,IAAkB,EAAE,EAAE;YACtE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YACnD,MAAM,GAAG,GAAG,YAAY,CAAC;YAEzB,OAAO,CAAC,SAAS,GAAG;gBAChB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;aACxB,CAAC;YAEF,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,YAAY,CAAC,EAAE,CAAC;gBACxD,OAAO,IAAI,EAAE,CAAC;YAClB,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS,CAAC,EAAE,CAAC;gBACzD,OAAO,IAAI,EAAE,CAAC;YAClB,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;YAC5D,QAAQ,CAAC,GAAG,GAAG,GAAG,CAAC;YACnB,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;YAG3D,IAAI,CAAC;gBACD,IAAI,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC3C,MAAM,IAAI,qBAAc,CAAC,+CAA+C,CAAC,CAAC;gBAC9E,CAAC;gBAED,MAAM,IAAI,CAAC,0BAA0B,CACjC,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,QAAQ,CACX,CAAC;gBAEF,wBAAwB;gBACxB,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC;gBACjC,QAAQ,CAAC,GAAG,GAAG,UAEX,KAAW,EACX,QAAyB,EACzB,EAAe;oBAEf,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,EAAE,CAAC;wBAC9B,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;oBACjE,CAAC;oBAED,MAAM,QAAQ,GACV,OAAO,CAAC,SAAS,EAAE,QAAQ,IAAI,EAAE,CAAC;oBACtC,QAAQ,CAAC,kBAAkB,GAAG,QAAQ,CAAC,UAAU,CAAC;oBAElD,6CAA6C;oBAC7C,IAAI,KAAK,EAAE,CAAC;wBACR,IAAI,CAAC;4BACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gCAC5B,wCAAwC;gCACxC,IAAI,CAAC;oCACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;oCAChC,QAAgB,CAAC,IAAI,GAAG,MAAM,CAAC;gCACpC,CAAC;gCAAC,MAAM,CAAC;oCACL,0CAA0C;oCACzC,QAAgB,CAAC,IAAI,GAAG,KAAK,CAAC;gCACnC,CAAC;4BACL,CAAC;iCAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gCAChC,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gCACnC,wCAAwC;gCACxC,IAAI,CAAC;oCACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;oCAC9B,QAAgB,CAAC,IAAI,GAAG,MAAM,CAAC;gCACpC,CAAC;gCAAC,MAAM,CAAC;oCACL,0CAA0C;oCACzC,QAAgB,CAAC,IAAI,GAAG,GAAG,CAAC;gCACjC,CAAC;4BACL,CAAC;iCAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gCAClC,QAAgB,CAAC,IAAI,GAAG,KAAK,CAAC;4BACnC,CAAC;wBACL,CAAC;wBAAC,OAAO,KAAK,EAAE,CAAC;wBACjB,CAAC;oBACL,CAAC;oBAED,MAAM,OAAO,GAAG;wBACZ,aAAa,EAAE,IAAI,CAAC,MAAO;wBAC3B,cAAc,EAAE,kBAAkB;qBACrC,CAAC;oBAEF,IAAI,OAAO,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC;wBAC/B,QAAQ,CAAC,eAAe;4BACpB,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC;oBACjD,CAAC;oBAED,MAAM,OAAO,GAAyB;wBAClC,KAAK,EAAE,QAAQ;wBACf,MAAM,EAAE,CAAC;wBACT,YAAY,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO;wBACxC,QAAQ,EAAE,QAA2B;qBACxC,CAAC;oBAEF,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,GAAQ,EAAE,EAAE;wBAClD,MAAM,GAAG,CAAC;oBACd,CAAC,CAAC,CAAC;oBACH,kCAAkC;oBAGlC,kCAAkC;oBAClC,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;wBAC9B,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;oBAC5D,CAAC;oBACD,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;wBACjC,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;oBAC3D,CAAC;oBACD,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;gBACjE,CAAwB,CAAC;gBAEzB,IAAI,EAAE,CAAC;YACX,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBAClB,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACtB,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,OAAO,EAAE,IAAI;iBAChB,CAAC,CAAC;gBACH,OAAO;YACX,CAAC;QACL,CAAC,CAAC;IACN,CAAC;CAEJ;AA5MD,kDA4MC"}
package/jest.config.js ADDED
@@ -0,0 +1,14 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ roots: ['<rootDir>/test'],
5
+ testMatch: ['**/*.test.ts'],
6
+ transform: {
7
+ '^.+\\.ts$': 'ts-jest',
8
+ },
9
+ moduleFileExtensions: ['ts', 'js', 'json'],
10
+ collectCoverageFrom: ['src/**/*.ts'],
11
+ coverageDirectory: 'coverage',
12
+ testTimeout: 10000,
13
+ };
14
+
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@usageflow/express",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "UsageFlow plugin for Express applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
- "test": "jest",
9
+ "test": "tsx --test test/plugin.test.ts",
10
10
  "prepare": "npm run build",
11
11
  "prepublishOnly": "npm run build"
12
12
  },
@@ -14,7 +14,8 @@
14
14
  "access": "public"
15
15
  },
16
16
  "dependencies": {
17
- "@usageflow/core": "^0.2.5",
17
+ "@usageflow/core": "^0.3.0",
18
+ "@usageflow/logger": "^0.1.1",
18
19
  "express": "^4.18.0",
19
20
  "ws": "^8.16.0"
20
21
  },
@@ -29,9 +30,7 @@
29
30
  "@types/node": "^20.0.0",
30
31
  "@types/ws": "^8.5.10",
31
32
  "typescript": "^5.0.0",
32
- "jest": "^29.0.0",
33
- "@types/jest": "^29.0.0",
34
- "ts-jest": "^29.0.0"
33
+ "tsx": "^4.7.0"
35
34
  },
36
35
  "peerDependencies": {
37
36
  "express": ">=4.17.0"
package/src/plugin.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, NextFunction } from "express";
2
- import { UsageFlowAPI, Route, RequestMetadata, RequestForAllocation, UsageFlowRequest, UsageFlowAPIConfig } from "@usageflow/core";
2
+ import { UsageFlowAPI, Route, RequestMetadata, RequestForAllocation, UsageFlowRequest, UsageFlowAPIConfig, UsageFlowError } from "@usageflow/core";
3
3
 
4
4
 
5
5
 
@@ -61,6 +61,7 @@ export class ExpressUsageFlowAPI extends UsageFlowAPI {
61
61
  metadata: RequestMetadata,
62
62
  request: Request,
63
63
  response: Response,
64
+ hasLimit: boolean,
64
65
  ): Promise<void> {
65
66
  if (!this.apiKey) {
66
67
  throw new Error("API key not initialized");
@@ -73,7 +74,7 @@ export class ExpressUsageFlowAPI extends UsageFlowAPI {
73
74
  duration: 1000
74
75
  };
75
76
 
76
- await this.allocationRequest(request as unknown as UsageFlowRequest, payload, metadata);
77
+ await this.allocationRequest(request as unknown as UsageFlowRequest, payload, metadata, hasLimit);
77
78
  }
78
79
 
79
80
  public createMiddleware(routes: Route[], whitelistRoutes: Route[] = []) {
@@ -101,14 +102,20 @@ export class ExpressUsageFlowAPI extends UsageFlowAPI {
101
102
 
102
103
  const metadata = await this.collectRequestMetadata(request);
103
104
  metadata.url = url;
104
- let ledgerId = this.guessLedgerId(request);
105
+ const { ledgerId, hasLimit } = this.guessLedgerId(request);
106
+
105
107
 
106
108
  try {
109
+ if (this.blockedEndpoints.includes(ledgerId)) {
110
+ throw new UsageFlowError(`Endpoint is been blocked by the administrator`);
111
+ }
112
+
107
113
  await this.executeRequestWithMetadata(
108
114
  ledgerId,
109
115
  metadata,
110
116
  request,
111
117
  response,
118
+ hasLimit,
112
119
  );
113
120
 
114
121
  // Capture response data
@@ -0,0 +1,201 @@
1
+ import { test, describe, beforeEach, afterEach } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { ExpressUsageFlowAPI } from '../src/plugin';
4
+ import { Route } from '@usageflow/core';
5
+
6
+ describe('ExpressUsageFlowAPI', () => {
7
+ let api: ExpressUsageFlowAPI;
8
+
9
+ beforeEach(() => {
10
+ api = new ExpressUsageFlowAPI({
11
+ apiKey: 'test-api-key',
12
+ poolSize: 1
13
+ });
14
+ // Mock socket manager
15
+ api.socketManager = {
16
+ connected: false,
17
+ async connect() { this.connected = true; },
18
+ isConnected() { return this.connected; },
19
+ async sendAsync<T>(payload: any): Promise<any> {
20
+ return { type: 'success', payload: { allocationId: 'test-id' } };
21
+ },
22
+ send() { },
23
+ close() { },
24
+ destroy() { }
25
+ } as any;
26
+ });
27
+
28
+ afterEach(() => {
29
+ api.destroy();
30
+ });
31
+
32
+ test('should create middleware', () => {
33
+ const routes: Route[] = [
34
+ { method: 'GET', url: '/users' }
35
+ ];
36
+
37
+ const middleware = api.createMiddleware(routes);
38
+ assert.strictEqual(typeof middleware, 'function');
39
+ assert.strictEqual(middleware.length, 3); // Express middleware signature
40
+ });
41
+
42
+ test('should create middleware with whitelist', () => {
43
+ const routes: Route[] = [
44
+ { method: '*', url: '*' }
45
+ ];
46
+ const whitelistRoutes: Route[] = [
47
+ { method: 'GET', url: '/health' }
48
+ ];
49
+
50
+ const middleware = api.createMiddleware(routes, whitelistRoutes);
51
+ assert.strictEqual(typeof middleware, 'function');
52
+ });
53
+
54
+ test.skip('should skip whitelisted routes', async () => {
55
+ const routes: Route[] = [
56
+ { method: '*', url: '*' }
57
+ ];
58
+ const whitelistRoutes: Route[] = [
59
+ { method: 'GET', url: '/health' }
60
+ ];
61
+
62
+ const middleware = api.createMiddleware(routes, whitelistRoutes);
63
+
64
+ const req: any = {
65
+ method: 'GET',
66
+ url: '/health',
67
+ path: '/health',
68
+ route: { path: '/health' },
69
+ headers: {},
70
+ query: {},
71
+ params: {},
72
+ body: {},
73
+ app: { _router: { stack: [] } },
74
+ usageflow: undefined
75
+ };
76
+
77
+ const res: any = {
78
+ statusCode: 200,
79
+ end: (chunk?: any, encoding?: any, cb?: any) => {
80
+ if (typeof chunk === 'function') {
81
+ chunk();
82
+ } else if (typeof encoding === 'function') {
83
+ encoding();
84
+ } else if (cb) {
85
+ cb();
86
+ }
87
+ }
88
+ };
89
+
90
+ let nextCalled = false;
91
+ const next = () => { nextCalled = true; };
92
+
93
+ await middleware(req, res, next);
94
+
95
+ assert.strictEqual(nextCalled, true);
96
+ assert.strictEqual(req.usageflow, undefined);
97
+ });
98
+
99
+ test('should monitor matching routes', async () => {
100
+ const routes: Route[] = [
101
+ { method: 'GET', url: '/users' }
102
+ ];
103
+
104
+ const middleware = api.createMiddleware(routes);
105
+
106
+ const req: any = {
107
+ method: 'GET',
108
+ url: '/users',
109
+ path: '/users',
110
+ route: { path: '/users' },
111
+ headers: {},
112
+ query: {},
113
+ params: {},
114
+ body: {},
115
+ ip: '127.0.0.1',
116
+ originalUrl: '/users',
117
+ app: { _router: { stack: [] } },
118
+ usageflow: undefined
119
+ };
120
+
121
+ const res: any = {
122
+ statusCode: 200,
123
+ end: (chunk?: any, encoding?: any, cb?: any) => {
124
+ if (typeof chunk === 'function') {
125
+ chunk();
126
+ } else if (typeof encoding === 'function') {
127
+ encoding();
128
+ } else if (cb) {
129
+ cb();
130
+ }
131
+ }
132
+ };
133
+
134
+ let nextCalled = false;
135
+ const next = () => { nextCalled = true; };
136
+
137
+ await middleware(req, res, next);
138
+
139
+ assert.strictEqual(nextCalled, true);
140
+ assert.ok(req.usageflow);
141
+ assert.ok(req.usageflow.startTime);
142
+ });
143
+
144
+ test.skip('should handle errors', async () => {
145
+ // Make socket manager throw error
146
+ api.socketManager = {
147
+ connected: false,
148
+ async connect() { this.connected = true; },
149
+ isConnected() { return this.connected; },
150
+ async sendAsync<T>(payload: any): Promise<any> {
151
+ throw new Error('Connection failed');
152
+ },
153
+ send() { },
154
+ close() { },
155
+ destroy() { }
156
+ } as any;
157
+
158
+ const routes: Route[] = [
159
+ { method: 'GET', url: '/users' }
160
+ ];
161
+
162
+ const middleware = api.createMiddleware(routes);
163
+
164
+ const req: any = {
165
+ method: 'GET',
166
+ url: '/users',
167
+ path: '/users',
168
+ route: { path: '/users' },
169
+ headers: {},
170
+ query: {},
171
+ params: {},
172
+ body: {},
173
+ ip: '127.0.0.1',
174
+ originalUrl: '/users',
175
+ app: { _router: { stack: [] } },
176
+ usageflow: undefined
177
+ };
178
+
179
+ let statusCode = 200;
180
+ let responseBody: any = null;
181
+ const res: any = {
182
+ statusCode: 200,
183
+ status: (code: number) => {
184
+ statusCode = code;
185
+ return res;
186
+ },
187
+ json: (body: any) => {
188
+ responseBody = body;
189
+ },
190
+ end: () => { }
191
+ };
192
+
193
+ const next = () => { };
194
+
195
+ await middleware(req, res, next);
196
+
197
+ assert.strictEqual(statusCode, 400);
198
+ assert.strictEqual(responseBody.blocked, true);
199
+ });
200
+ });
201
+
package/tsconfig.json CHANGED
@@ -5,5 +5,6 @@
5
5
  "rootDir": "./src"
6
6
  },
7
7
  "include": ["src/**/*"],
8
+ "exclude": ["test/**/*", "node_modules", "dist"],
8
9
  "references": [{ "path": "../core" }]
9
10
  }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./test-compiled",
5
+ "rootDir": "./test",
6
+ "noEmit": false
7
+ },
8
+ "include": ["test/**/*"],
9
+ "exclude": ["src/**/*"]
10
+ }