@usageflow/core 0.2.5 → 0.2.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/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@usageflow/core",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Core functionality for UsageFlow integrations",
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/base.test.ts test/socket.test.ts test/types.test.ts",
10
10
  "prepare": "npm run build",
11
11
  "prepublishOnly": "npm run build"
12
12
  },
@@ -15,7 +15,8 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "axios": "^1.6.0",
18
- "ws": "^8.16.0"
18
+ "ws": "^8.16.0",
19
+ "@usageflow/logger": "^0.1.1"
19
20
  },
20
21
  "homepage": "https://usageflow.io",
21
22
  "repository": {
@@ -26,9 +27,7 @@
26
27
  "devDependencies": {
27
28
  "@types/node": "^20.0.0",
28
29
  "@types/ws": "^8.5.10",
29
- "typescript": "^5.0.0",
30
- "jest": "^29.0.0",
31
- "@types/jest": "^29.0.0",
32
- "ts-jest": "^29.0.0"
30
+ "tsx": "^4.20.6",
31
+ "typescript": "^5.0.0"
33
32
  }
34
33
  }
package/src/base.ts CHANGED
@@ -11,17 +11,26 @@ import {
11
11
  UsageFlowAPIConfig,
12
12
  } from "./types";
13
13
  import { UsageFlowSocketManger } from "./socket";
14
+ import { UsageFlowLogger, usLogger } from "@usageflow/logger";
14
15
 
15
16
  export abstract class UsageFlowAPI {
16
17
  protected apiKey: string | null = null;
17
18
  protected usageflowUrl: string = "https://api.usageflow.io";
18
- protected webServer: 'express' | 'fastify' | 'nestjs' = 'express';
19
+ protected webServer: "express" | "fastify" | "nestjs" = "express";
19
20
  protected apiConfigs: UsageFlowConfig[] = [];
20
21
  private configUpdateInterval: NodeJS.Timeout | null = null;
21
- socketManager: UsageFlowSocketManger | null = null;
22
+ private socketManager: UsageFlowSocketManger | null = null;
22
23
  private applicationId: boolean = false;
23
-
24
- constructor(config: UsageFlowAPIConfig = { apiKey: '', poolSize: 5 }) {
24
+ private logger: UsageFlowLogger;
25
+
26
+ constructor(config: UsageFlowAPIConfig = { apiKey: "", poolSize: 5 }) {
27
+ // Initialize logger with service name
28
+ this.logger = usLogger({ service: 'USAGEFLOW_BASE', pretty: true, silent: process.env.UF_LOGS_ENABLED !== 'true' });
29
+ // Set default context for all logs
30
+ this.logger.setContext({
31
+ component: 'UsageFlowAPI',
32
+ webServer: this.webServer,
33
+ });
25
34
  this.init(config.apiKey, this.usageflowUrl, config.poolSize);
26
35
  }
27
36
 
@@ -41,18 +50,30 @@ export abstract class UsageFlowAPI {
41
50
  // this.startConfigUpdater();
42
51
  this.socketManager = new UsageFlowSocketManger(apiKey, poolSize);
43
52
  // Connect the socket manager
44
- this.socketManager.connect().catch((error) => {
45
- console.error("[UsageFlow] Failed to establish WebSocket connection:", error);
46
- }).then(() => {
47
- this.startConfigUpdater();
48
- });
49
-
53
+ this.socketManager
54
+ .connect()
55
+ .catch((error) => {
56
+ this.logger.error(
57
+ "[UsageFlow] Failed to establish WebSocket connection:",
58
+ error,
59
+ );
60
+ })
61
+ .then(() => {
62
+ this.logger.info("WebSocket connection established");
63
+ if (this.socketManager?.isConnected()) {
64
+ this.startConfigUpdater();
65
+ } else {
66
+ this.logger.error("WebSocket connection failed");
67
+ }
68
+ });
50
69
 
51
70
  return this;
52
71
  }
53
72
 
54
- public setWebServer(webServer: 'express' | 'fastify' | 'nestjs'): void {
73
+ public setWebServer(webServer: "express" | "fastify" | "nestjs"): void {
55
74
  this.webServer = webServer;
75
+ // Update logger context with new web server
76
+ this.logger.setContext({ webServer });
56
77
  }
57
78
 
58
79
  public getApiKey(): string | null {
@@ -67,11 +88,12 @@ export abstract class UsageFlowAPI {
67
88
  * Start background config update process
68
89
  */
69
90
  private startConfigUpdater(): void {
91
+ this.logger.info("Starting background config update process");
70
92
  if (this.configUpdateInterval) {
71
93
  clearInterval(this.configUpdateInterval);
72
94
  }
73
95
 
74
- this.fetchApiPolicies().catch(console.error);
96
+ this.fetchApiPolicies().catch(this.logger.error);
75
97
  this.configUpdateInterval = setInterval(async () => {
76
98
  await this.fetchApiPolicies();
77
99
  }, 60000);
@@ -80,13 +102,12 @@ export abstract class UsageFlowAPI {
80
102
  }
81
103
 
82
104
  public getRoutePattern(request: UsageFlowRequest): string {
83
-
84
- if (this.webServer === 'fastify') {
85
- return request?.routeOptions?.url || request.url || '';
105
+ if (this.webServer === "fastify") {
106
+ return request?.routeOptions?.url || request.url || "";
86
107
  }
87
108
 
88
109
  // For NestJS, prioritize route.path with baseUrl
89
- if (this.webServer === 'nestjs') {
110
+ if (this.webServer === "nestjs") {
90
111
  // Method 1: Use request.route.path (available after route matching in NestJS)
91
112
  if (request.route?.path) {
92
113
  const baseUrl = request.baseUrl || "";
@@ -102,7 +123,9 @@ export abstract class UsageFlowAPI {
102
123
  let path = request.path || request.url?.split("?")[0] || "/";
103
124
 
104
125
  // Split path into segments and replace matching segments with param names
105
- const pathSegments = path.split("/").filter(segment => segment !== "");
126
+ const pathSegments = path
127
+ .split("/")
128
+ .filter((segment) => segment !== "");
106
129
  const paramEntries = Object.entries(request.params);
107
130
 
108
131
  // Replace segments that match param values with :paramName
@@ -121,21 +144,22 @@ export abstract class UsageFlowAPI {
121
144
  }
122
145
  }
123
146
 
124
- const routePattern = request.route?.path || request.app._router.stack.find((route: any) => {
125
- // a => a.path == request.url
126
- if (!route.route) return false;
127
- if (route.path) {
128
- return route.path == request.url;
129
- }
130
-
131
- if (route.regexp) {
132
- const patterned = route.regexp.test(request.url);
133
- if (patterned) {
134
- return patterned;
147
+ const routePattern =
148
+ request.route?.path ||
149
+ request.app._router.stack.find((route: any) => {
150
+ // a => a.path == request.url
151
+ if (!route.route) return false;
152
+ if (route.path) {
153
+ return route.path == request.url;
135
154
  }
136
- }
137
155
 
138
- })?.route?.path
156
+ if (route.regexp) {
157
+ const patterned = route.regexp.test(request.url);
158
+ if (patterned) {
159
+ return patterned;
160
+ }
161
+ }
162
+ })?.route?.path;
139
163
 
140
164
  if (routePattern) {
141
165
  return routePattern;
@@ -163,7 +187,9 @@ export abstract class UsageFlowAPI {
163
187
  if (layer.route) {
164
188
  const route = layer.route;
165
189
  const routePath = route.path;
166
- const routeMethods = Object.keys(route.methods).map(m => m.toLowerCase());
190
+ const routeMethods = Object.keys(route.methods).map((m) =>
191
+ m.toLowerCase(),
192
+ );
167
193
 
168
194
  // Check if method matches and path pattern matches
169
195
  if (routeMethods.includes(method) || routeMethods.includes("*")) {
@@ -173,7 +199,9 @@ export abstract class UsageFlowAPI {
173
199
  if (request.params && Object.keys(request.params).length > 0) {
174
200
  // Check if route path contains the param names
175
201
  const paramNames = Object.keys(request.params);
176
- const routeHasParams = paramNames.some(param => routePath.includes(`:${param}`));
202
+ const routeHasParams = paramNames.some((param) =>
203
+ routePath.includes(`:${param}`),
204
+ );
177
205
  if (routeHasParams) {
178
206
  return pattern;
179
207
  }
@@ -184,7 +212,11 @@ export abstract class UsageFlowAPI {
184
212
  }
185
213
  }
186
214
  }
187
- } else if (layer.name === "router" && layer.handle && layer.handle.stack) {
215
+ } else if (
216
+ layer.name === "router" &&
217
+ layer.handle &&
218
+ layer.handle.stack
219
+ ) {
188
220
  // Handle router middleware (nested routers)
189
221
  const mountPath = layer.regexp.source
190
222
  .replace("\\/?", "")
@@ -198,19 +230,30 @@ export abstract class UsageFlowAPI {
198
230
  if (subLayer.route) {
199
231
  const route = subLayer.route;
200
232
  const routePath = route.path;
201
- const routeMethods = Object.keys(route.methods).map(m => m.toLowerCase());
202
-
203
- if (routeMethods.includes(method) || routeMethods.includes("*")) {
233
+ const routeMethods = Object.keys(route.methods).map((m) =>
234
+ m.toLowerCase(),
235
+ );
236
+
237
+ if (
238
+ routeMethods.includes(method) ||
239
+ routeMethods.includes("*")
240
+ ) {
204
241
  const fullPath = mountPath + routePath;
205
242
  // Check if this route matches by examining params
206
- if (request.params && Object.keys(request.params).length > 0) {
243
+ if (
244
+ request.params &&
245
+ Object.keys(request.params).length > 0
246
+ ) {
207
247
  const paramNames = Object.keys(request.params);
208
- const routeHasParams = paramNames.some(param => routePath.includes(`:${param}`));
248
+ const routeHasParams = paramNames.some((param) =>
249
+ routePath.includes(`:${param}`),
250
+ );
209
251
  if (routeHasParams) {
210
252
  return fullPath;
211
253
  }
212
254
  } else if (!routePath.includes(":")) {
213
- const currentPath = request.path || request.url?.split("?")[0] || "";
255
+ const currentPath =
256
+ request.path || request.url?.split("?")[0] || "";
214
257
  if (currentPath === fullPath || currentPath === routePath) {
215
258
  return fullPath;
216
259
  }
@@ -223,7 +266,10 @@ export abstract class UsageFlowAPI {
223
266
  }
224
267
  } catch (error) {
225
268
  // Silently fail and try next method
226
- console.debug("[UsageFlow] Could not extract route from router stack:", error);
269
+ console.debug(
270
+ "[UsageFlow] Could not extract route from router stack:",
271
+ error,
272
+ );
227
273
  }
228
274
 
229
275
  // Method 3: Reconstruct pattern from params and path
@@ -255,16 +301,18 @@ export abstract class UsageFlowAPI {
255
301
  return baseUrl + path || "/";
256
302
  }
257
303
 
258
- public guessLedgerId(request: UsageFlowRequest, overrideUrl?: string): string {
304
+ public guessLedgerId(
305
+ request: UsageFlowRequest,
306
+ overrideUrl?: string,
307
+ ): string {
259
308
  const method = request.method;
260
309
  const url = overrideUrl || this.getRoutePattern(request);
261
- const configs = this.apiConfigs
310
+ const configs = this.apiConfigs;
262
311
 
263
312
  if (!configs.length) {
264
313
  return `${method} ${url}`;
265
314
  }
266
315
 
267
-
268
316
  for (const config of configs) {
269
317
  const fieldName = config.identityFieldName!;
270
318
  const location = config.identityFieldLocation;
@@ -286,7 +334,10 @@ export abstract class UsageFlowAPI {
286
334
  }
287
335
  break;
288
336
  case "bearer_token":
289
- const authHeader = this.getHeaderValue(request.headers, 'authorization');
337
+ const authHeader = this.getHeaderValue(
338
+ request.headers,
339
+ "authorization",
340
+ );
290
341
  const token = this.extractBearerToken(authHeader || undefined);
291
342
  if (token) {
292
343
  const claims = this.decodeJwtUnverified(token);
@@ -311,31 +362,36 @@ export abstract class UsageFlowAPI {
311
362
 
312
363
  private async fetchApiPolicies(): Promise<void> {
313
364
  if (this.socketManager && this.socketManager.isConnected()) {
314
- const response = await this.socketManager.sendAsync<{ policies: UsageFlowConfig[], total: number }>({
365
+ const response = await this.socketManager.sendAsync<{
366
+ policies: UsageFlowConfig[];
367
+ total: number;
368
+ }>({
315
369
  type: "get_application_policies",
316
370
  payload: null,
317
371
  });
318
- if (response.type === 'success') {
372
+ if (response.type === "success") {
319
373
  this.apiConfigs = response.payload?.policies || [];
320
374
  }
321
375
  }
322
376
  }
323
377
 
324
- async useAllocationRequest(
325
- payload: RequestForAllocation,
326
- ): Promise<void> {
378
+ async useAllocationRequest(payload: RequestForAllocation): Promise<void> {
327
379
  if (this.socketManager && this.socketManager.isConnected()) {
328
- this.socketManager.sendAsync<any>({
329
- type: "use_allocation",
330
- payload
331
- }).catch((error) => {
332
- console.error("[UsageFlow] Error sending finalization via WebSocket:", error);
333
- throw error;
334
- });
380
+ this.socketManager
381
+ .sendAsync<any>({
382
+ type: "use_allocation",
383
+ payload,
384
+ })
385
+ .catch((error) => {
386
+ console.error(
387
+ "[UsageFlow] Error sending finalization via WebSocket:",
388
+ error,
389
+ );
390
+ throw error;
391
+ });
335
392
  }
336
393
  }
337
394
 
338
-
339
395
  async allocationRequest(
340
396
  request: UsageFlowRequest,
341
397
  payload: RequestForAllocation,
@@ -345,21 +401,28 @@ export abstract class UsageFlowAPI {
345
401
  try {
346
402
  const allocationResponse = await this.socketManager.sendAsync<any>({
347
403
  type: "request_for_allocation",
348
- payload
404
+ payload,
349
405
  });
350
406
 
351
- if (allocationResponse.type === 'error') {
352
- throw new Error(allocationResponse.message || allocationResponse.error);
407
+ if (allocationResponse.type === "error") {
408
+ throw new Error(
409
+ allocationResponse.message || allocationResponse.error,
410
+ );
353
411
  }
354
- if (allocationResponse.type === 'success') {
412
+ if (allocationResponse.type === "success") {
355
413
  request.usageflow!.eventId = allocationResponse.payload.allocationId;
356
414
  request.usageflow!.metadata = metadata;
357
415
  return;
358
416
  } else {
359
- throw new Error(allocationResponse.message || "Unknown error occurred");
417
+ throw new Error(
418
+ allocationResponse.message || "Unknown error occurred",
419
+ );
360
420
  }
361
421
  } catch (error: any) {
362
- console.error("[UsageFlow] WebSocket allocation failed, falling back to HTTP:", error);
422
+ console.error(
423
+ "[UsageFlow] WebSocket allocation failed, falling back to HTTP:",
424
+ error,
425
+ );
363
426
  throw error;
364
427
  // Fall through to HTTP request
365
428
  }
@@ -468,8 +531,8 @@ export abstract class UsageFlowAPI {
468
531
  }
469
532
 
470
533
  // Check if it's a bearer token
471
- const parts = authHeader.split(' ');
472
- if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') {
534
+ const parts = authHeader.split(" ");
535
+ if (parts.length !== 2 || parts[0].toLowerCase() !== "bearer") {
473
536
  return null;
474
537
  }
475
538
 
@@ -479,14 +542,19 @@ export abstract class UsageFlowAPI {
479
542
  /**
480
543
  * Get header value from headers object (handles both Record and Headers types)
481
544
  */
482
- private getHeaderValue(headers: Record<string, string | string[] | undefined> | Headers, key: string): string | null {
545
+ private getHeaderValue(
546
+ headers: Record<string, string | string[] | undefined> | Headers,
547
+ key: string,
548
+ ): string | null {
483
549
  // Check if it's a Headers object (from Fetch API) by checking for the 'get' method
484
- if (headers && typeof (headers as any).get === 'function') {
550
+ if (headers && typeof (headers as any).get === "function") {
485
551
  return (headers as any).get(key) || null;
486
552
  }
487
553
  // Otherwise, treat it as a Record
488
- const value = (headers as Record<string, string | string[] | undefined>)[key];
489
- if (typeof value === 'string') {
554
+ const value = (headers as Record<string, string | string[] | undefined>)[
555
+ key
556
+ ];
557
+ if (typeof value === "string") {
490
558
  return value;
491
559
  }
492
560
  if (Array.isArray(value) && value.length > 0) {
@@ -503,14 +571,14 @@ export abstract class UsageFlowAPI {
503
571
  public decodeJwtUnverified(token: string): Record<string, any> | null {
504
572
  try {
505
573
  // Split the token into parts
506
- const parts = token.split('.');
574
+ const parts = token.split(".");
507
575
  if (parts.length !== 3) {
508
576
  return null;
509
577
  }
510
578
 
511
579
  // Decode the payload (claims)
512
580
  const payload = parts[1];
513
- const decoded = Buffer.from(payload, 'base64').toString('utf-8');
581
+ const decoded = Buffer.from(payload, "base64").toString("utf-8");
514
582
  return JSON.parse(decoded);
515
583
  } catch (error) {
516
584
  return null;