@usageflow/core 0.4.2 → 0.4.3
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/dist/base.d.ts +20 -1
- package/dist/base.js +215 -9
- package/dist/base.js.map +1 -1
- package/dist/socket.js +1 -0
- package/dist/socket.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/base.ts +222 -9
- package/src/socket.ts +1 -0
- package/src/types.ts +8 -0
- package/test/base.test.ts +695 -17
- package/test/src/types.d.ts +3 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/base.ts
CHANGED
|
@@ -318,7 +318,7 @@ export abstract class UsageFlowAPI {
|
|
|
318
318
|
public guessLedgerId(
|
|
319
319
|
request: UsageFlowRequest,
|
|
320
320
|
overrideUrl?: string,
|
|
321
|
-
): { ledgerId: string, hasLimit: boolean } {
|
|
321
|
+
): { ledgerId: string, hasLimit: boolean, responseTrackingField?: string } {
|
|
322
322
|
const method = request.method;
|
|
323
323
|
const url = overrideUrl || this.getRoutePattern(request);
|
|
324
324
|
const configs = this.apiConfigs;
|
|
@@ -331,23 +331,25 @@ export abstract class UsageFlowAPI {
|
|
|
331
331
|
const fieldName = config.identityFieldName!;
|
|
332
332
|
const location = config.identityFieldLocation;
|
|
333
333
|
const hasLimit = config.hasRateLimit || false;
|
|
334
|
+
const isResponseTrackingEnabled = config.isResponseTrackingEnabled;
|
|
335
|
+
const responseTrackingField = isResponseTrackingEnabled ? config.responseTrackingField : undefined;
|
|
334
336
|
|
|
335
337
|
if (method === config.method && url === config.url) {
|
|
336
338
|
|
|
337
339
|
switch (location) {
|
|
338
340
|
case "path_params":
|
|
339
341
|
if (request.params?.[fieldName]) {
|
|
340
|
-
return { ledgerId: `${method} ${url} ${request.params[fieldName]}`, hasLimit };
|
|
342
|
+
return { ledgerId: `${method} ${url} ${request.params[fieldName]}`, hasLimit, responseTrackingField };
|
|
341
343
|
}
|
|
342
344
|
break;
|
|
343
345
|
case "query_params":
|
|
344
346
|
if (request.query?.[fieldName]) {
|
|
345
|
-
return { ledgerId: `${method} ${url} ${request.query[fieldName]}`, hasLimit };
|
|
347
|
+
return { ledgerId: `${method} ${url} ${request.query[fieldName]}`, hasLimit, responseTrackingField };
|
|
346
348
|
}
|
|
347
349
|
break;
|
|
348
350
|
case "body":
|
|
349
351
|
if (request.body?.[fieldName]) {
|
|
350
|
-
return { ledgerId: `${method} ${url} ${request.body[fieldName]}`, hasLimit };
|
|
352
|
+
return { ledgerId: `${method} ${url} ${request.body[fieldName]}`, hasLimit, responseTrackingField };
|
|
351
353
|
}
|
|
352
354
|
break;
|
|
353
355
|
case "bearer_token":
|
|
@@ -359,7 +361,7 @@ export abstract class UsageFlowAPI {
|
|
|
359
361
|
if (token) {
|
|
360
362
|
const claims = this.decodeJwtUnverified(token);
|
|
361
363
|
if (claims?.[fieldName]) {
|
|
362
|
-
return { ledgerId: `${method} ${url} ${claims[fieldName]}`, hasLimit };
|
|
364
|
+
return { ledgerId: `${method} ${url} ${claims[fieldName]}`, hasLimit, responseTrackingField };
|
|
363
365
|
}
|
|
364
366
|
}
|
|
365
367
|
break;
|
|
@@ -367,7 +369,7 @@ export abstract class UsageFlowAPI {
|
|
|
367
369
|
case "headers": {
|
|
368
370
|
const headerValue = this.getHeaderValue(request.headers, fieldName);
|
|
369
371
|
if (headerValue) {
|
|
370
|
-
return { ledgerId: `${method} ${url} ${headerValue}`, hasLimit };
|
|
372
|
+
return { ledgerId: `${method} ${url} ${headerValue}`, hasLimit, responseTrackingField };
|
|
371
373
|
}
|
|
372
374
|
break;
|
|
373
375
|
}
|
|
@@ -381,7 +383,7 @@ export abstract class UsageFlowAPI {
|
|
|
381
383
|
if (cookieValue) {
|
|
382
384
|
const claims = this.decodeJwtUnverified(cookieValue);
|
|
383
385
|
if (claims?.[claim]) {
|
|
384
|
-
return { ledgerId: `${method} ${url} ${claims[claim]}`, hasLimit };
|
|
386
|
+
return { ledgerId: `${method} ${url} ${claims[claim]}`, hasLimit, responseTrackingField };
|
|
385
387
|
}
|
|
386
388
|
}
|
|
387
389
|
break;
|
|
@@ -398,7 +400,7 @@ export abstract class UsageFlowAPI {
|
|
|
398
400
|
}
|
|
399
401
|
|
|
400
402
|
if (cookieValue) {
|
|
401
|
-
return { ledgerId: `${method} ${url} ${cookieValue}`, hasLimit };
|
|
403
|
+
return { ledgerId: `${method} ${url} ${cookieValue}`, hasLimit, responseTrackingField };
|
|
402
404
|
}
|
|
403
405
|
break;
|
|
404
406
|
}
|
|
@@ -406,7 +408,7 @@ export abstract class UsageFlowAPI {
|
|
|
406
408
|
}
|
|
407
409
|
}
|
|
408
410
|
|
|
409
|
-
return { ledgerId: `${method} ${url}`, hasLimit: false };
|
|
411
|
+
return { ledgerId: `${method} ${url}`, hasLimit: false, responseTrackingField: undefined };
|
|
410
412
|
}
|
|
411
413
|
|
|
412
414
|
private async fetchApiPolicies(): Promise<void> {
|
|
@@ -476,6 +478,7 @@ export abstract class UsageFlowAPI {
|
|
|
476
478
|
payload: RequestForAllocation,
|
|
477
479
|
metadata: RequestMetadata,
|
|
478
480
|
hasLimit: boolean,
|
|
481
|
+
responseTrackingField?: string,
|
|
479
482
|
): Promise<void> {
|
|
480
483
|
if (this.socketManager && this.socketManager.isConnected()) {
|
|
481
484
|
try {
|
|
@@ -508,6 +511,7 @@ export abstract class UsageFlowAPI {
|
|
|
508
511
|
});
|
|
509
512
|
request.usageflow!.eventId = payload.allocationId;
|
|
510
513
|
request.usageflow!.metadata = metadata;
|
|
514
|
+
request.usageflow!.responseTrackingField = responseTrackingField;
|
|
511
515
|
}
|
|
512
516
|
} catch (error: any) {
|
|
513
517
|
console.error(
|
|
@@ -684,6 +688,109 @@ export abstract class UsageFlowAPI {
|
|
|
684
688
|
return path.split('.').reduce((acc, part) => acc?.[part], object);
|
|
685
689
|
}
|
|
686
690
|
|
|
691
|
+
/**
|
|
692
|
+
* Extract a value from an object using a dot-notation path
|
|
693
|
+
* Supports nested paths like "data.amount" or "result.items[0].count"
|
|
694
|
+
* Supports wildcard array notation like "items[*].id" to iterate over arrays
|
|
695
|
+
* @param obj - The object to extract the value from
|
|
696
|
+
* @param path - The dot-notation path to the value (e.g., "data.amount" or "items[*].id")
|
|
697
|
+
* @returns The value at the path, or undefined if not found. For wildcard arrays, returns an array of values.
|
|
698
|
+
*/
|
|
699
|
+
public getValueByPath(obj: any, path: string): any {
|
|
700
|
+
if (!obj || !path) {
|
|
701
|
+
return undefined;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Parse path into segments, preserving [*] as a special marker
|
|
705
|
+
const segments: Array<string | '*'> = [];
|
|
706
|
+
let currentSegment = '';
|
|
707
|
+
let i = 0;
|
|
708
|
+
|
|
709
|
+
while (i < path.length) {
|
|
710
|
+
if (path[i] === '[' && i + 1 < path.length && path[i + 1] === '*') {
|
|
711
|
+
// Found [*]
|
|
712
|
+
if (currentSegment) {
|
|
713
|
+
segments.push(currentSegment);
|
|
714
|
+
currentSegment = '';
|
|
715
|
+
}
|
|
716
|
+
segments.push('*');
|
|
717
|
+
i += 3; // Skip '[', '*', ']'
|
|
718
|
+
} else if (path[i] === '[') {
|
|
719
|
+
// Found indexed array access like [0]
|
|
720
|
+
if (currentSegment) {
|
|
721
|
+
segments.push(currentSegment);
|
|
722
|
+
currentSegment = '';
|
|
723
|
+
}
|
|
724
|
+
// Extract the index
|
|
725
|
+
let index = '';
|
|
726
|
+
i++; // Skip '['
|
|
727
|
+
while (i < path.length && path[i] !== ']') {
|
|
728
|
+
index += path[i];
|
|
729
|
+
i++;
|
|
730
|
+
}
|
|
731
|
+
if (index) {
|
|
732
|
+
segments.push(index);
|
|
733
|
+
}
|
|
734
|
+
i++; // Skip ']'
|
|
735
|
+
} else if (path[i] === '.') {
|
|
736
|
+
if (currentSegment) {
|
|
737
|
+
segments.push(currentSegment);
|
|
738
|
+
currentSegment = '';
|
|
739
|
+
}
|
|
740
|
+
i++;
|
|
741
|
+
} else {
|
|
742
|
+
currentSegment += path[i];
|
|
743
|
+
i++;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (currentSegment) {
|
|
748
|
+
segments.push(currentSegment);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Recursive helper function to process segments
|
|
752
|
+
const processSegments = (currentObj: any, segs: Array<string | '*'>, segIndex: number): any => {
|
|
753
|
+
if (currentObj === null || currentObj === undefined) {
|
|
754
|
+
return undefined;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// If we've processed all segments, return the current value
|
|
758
|
+
if (segIndex >= segs.length) {
|
|
759
|
+
return currentObj;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const segment = segs[segIndex];
|
|
763
|
+
|
|
764
|
+
// Handle wildcard array iteration
|
|
765
|
+
if (segment === '*') {
|
|
766
|
+
if (!Array.isArray(currentObj)) {
|
|
767
|
+
return undefined;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// If this is the last segment, return the first element of the array
|
|
771
|
+
if (segIndex === segs.length - 1) {
|
|
772
|
+
return currentObj.length > 0 ? currentObj[0] : undefined;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Otherwise, iterate over the array and return the first valid result
|
|
776
|
+
for (const item of currentObj) {
|
|
777
|
+
const result = processSegments(item, segs, segIndex + 1);
|
|
778
|
+
if (result !== undefined) {
|
|
779
|
+
return result;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return undefined;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Handle regular property access
|
|
787
|
+
const nextObj = currentObj[segment];
|
|
788
|
+
return processSegments(nextObj, segs, segIndex + 1);
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
return processSegments(obj, segments, 0);
|
|
792
|
+
}
|
|
793
|
+
|
|
687
794
|
/**
|
|
688
795
|
* Parse and extract a specific cookie value from the Cookie header
|
|
689
796
|
* @param headers The request headers object
|
|
@@ -753,4 +860,110 @@ export abstract class UsageFlowAPI {
|
|
|
753
860
|
claim: pickMatch[1],
|
|
754
861
|
};
|
|
755
862
|
}
|
|
863
|
+
|
|
864
|
+
public getResponseBody(response: Response): any {
|
|
865
|
+
if (response.body) {
|
|
866
|
+
return response.body;
|
|
867
|
+
}
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Extract schema from a JavaScript object/array
|
|
873
|
+
* Recursively processes objects, arrays, and primitives to build a schema structure
|
|
874
|
+
* @param obj - The object to extract schema from
|
|
875
|
+
* @param path - The current path in the object hierarchy (for nested objects)
|
|
876
|
+
* @returns Schema object with type information and paths
|
|
877
|
+
*/
|
|
878
|
+
public extractSchema(obj: any, path: string = ''): any {
|
|
879
|
+
// Handle null
|
|
880
|
+
if (obj === null || obj === undefined) {
|
|
881
|
+
return 'null';
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Handle arrays
|
|
885
|
+
if (Array.isArray(obj)) {
|
|
886
|
+
if (obj.length === 0) {
|
|
887
|
+
return 'array';
|
|
888
|
+
}
|
|
889
|
+
// Get schema of first element as representative
|
|
890
|
+
return {
|
|
891
|
+
type: 'array',
|
|
892
|
+
items: this.extractSchema(obj[0], path),
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Handle objects
|
|
897
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
898
|
+
const schema: Record<string, any> = {};
|
|
899
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
900
|
+
const fieldPath = path ? `${path}.${key}` : key;
|
|
901
|
+
|
|
902
|
+
if (value === null || value === undefined) {
|
|
903
|
+
schema[key] = {
|
|
904
|
+
type: 'null',
|
|
905
|
+
path: fieldPath,
|
|
906
|
+
};
|
|
907
|
+
} else if (typeof value === 'boolean') {
|
|
908
|
+
// Check bool before number (important for type detection)
|
|
909
|
+
schema[key] = {
|
|
910
|
+
type: 'boolean',
|
|
911
|
+
path: fieldPath,
|
|
912
|
+
};
|
|
913
|
+
} else if (typeof value === 'number') {
|
|
914
|
+
// Check if it's an integer or float
|
|
915
|
+
if (Number.isInteger(value)) {
|
|
916
|
+
schema[key] = {
|
|
917
|
+
type: 'integer',
|
|
918
|
+
path: fieldPath,
|
|
919
|
+
};
|
|
920
|
+
} else {
|
|
921
|
+
schema[key] = {
|
|
922
|
+
type: 'float',
|
|
923
|
+
path: fieldPath,
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
} else if (typeof value === 'string') {
|
|
927
|
+
schema[key] = {
|
|
928
|
+
type: 'string',
|
|
929
|
+
path: fieldPath,
|
|
930
|
+
};
|
|
931
|
+
} else if (Array.isArray(value)) {
|
|
932
|
+
schema[key] = {
|
|
933
|
+
type: 'array',
|
|
934
|
+
path: fieldPath,
|
|
935
|
+
items: value.length > 0 ? this.extractSchema(value[0], fieldPath) : 'unknown',
|
|
936
|
+
};
|
|
937
|
+
} else if (typeof value === 'object') {
|
|
938
|
+
schema[key] = {
|
|
939
|
+
type: 'object',
|
|
940
|
+
path: fieldPath,
|
|
941
|
+
properties: this.extractSchema(value, fieldPath),
|
|
942
|
+
};
|
|
943
|
+
} else {
|
|
944
|
+
schema[key] = {
|
|
945
|
+
type: typeof value,
|
|
946
|
+
path: fieldPath,
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
return schema;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Handle primitive types
|
|
955
|
+
if (typeof obj === 'boolean') {
|
|
956
|
+
return 'boolean';
|
|
957
|
+
} else if (typeof obj === 'number') {
|
|
958
|
+
if (Number.isInteger(obj)) {
|
|
959
|
+
return 'integer';
|
|
960
|
+
} else {
|
|
961
|
+
return 'float';
|
|
962
|
+
}
|
|
963
|
+
} else if (typeof obj === 'string') {
|
|
964
|
+
return 'string';
|
|
965
|
+
} else {
|
|
966
|
+
return typeof obj;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
756
969
|
}
|
package/src/socket.ts
CHANGED
|
@@ -11,6 +11,7 @@ interface PooledConnection {
|
|
|
11
11
|
export class UsageFlowSocketManger {
|
|
12
12
|
private connections: PooledConnection[] = [];
|
|
13
13
|
private wsUrl: string = "wss://api.usageflow.io/ws";
|
|
14
|
+
// private wsUrl: string = "ws://localhost:9000/ws";
|
|
14
15
|
private poolSize: number = 10; // Default pool size
|
|
15
16
|
private currentIndex: number = 0; // For round-robin selection
|
|
16
17
|
private connecting: boolean = false;
|
package/src/types.ts
CHANGED
|
@@ -5,6 +5,7 @@ declare global {
|
|
|
5
5
|
startTime: number;
|
|
6
6
|
eventId?: string;
|
|
7
7
|
metadata?: RequestMetadata;
|
|
8
|
+
responseTrackingField?: string;
|
|
8
9
|
};
|
|
9
10
|
baseUrl: string;
|
|
10
11
|
}
|
|
@@ -16,6 +17,8 @@ declare global {
|
|
|
16
17
|
startTime: number;
|
|
17
18
|
eventId?: string;
|
|
18
19
|
metadata?: RequestMetadata;
|
|
20
|
+
responseTrackingField?: string;
|
|
21
|
+
|
|
19
22
|
};
|
|
20
23
|
}
|
|
21
24
|
}
|
|
@@ -26,6 +29,7 @@ declare global {
|
|
|
26
29
|
startTime: number;
|
|
27
30
|
eventId?: string;
|
|
28
31
|
metadata?: RequestMetadata;
|
|
32
|
+
responseTrackingField?: string;
|
|
29
33
|
};
|
|
30
34
|
}
|
|
31
35
|
}
|
|
@@ -63,6 +67,9 @@ export interface UsageFlowConfig {
|
|
|
63
67
|
identityFieldName?: string;
|
|
64
68
|
identityFieldLocation?: string;
|
|
65
69
|
hasRateLimit?: boolean;
|
|
70
|
+
responseTrackingField?: string;
|
|
71
|
+
isResponseTrackingEnabled?: boolean;
|
|
72
|
+
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
export interface BlockedEndpoint {
|
|
@@ -140,6 +147,7 @@ export interface UsageFlowRequest {
|
|
|
140
147
|
eventId?: string;
|
|
141
148
|
metadata?: RequestMetadata;
|
|
142
149
|
startTime?: number;
|
|
150
|
+
responseTrackingField?: string;
|
|
143
151
|
};
|
|
144
152
|
routeOptions?: {
|
|
145
153
|
url: string;
|