@aforoai/metering 1.0.0 → 2.0.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 +71 -25
- package/dist/cjs/client.d.ts +33 -35
- package/dist/cjs/client.d.ts.map +1 -1
- package/dist/cjs/client.js +193 -147
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/idempotency.d.ts +9 -9
- package/dist/cjs/idempotency.js +9 -9
- package/dist/cjs/index.d.ts +8 -8
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +20 -19
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/limits.d.ts +47 -7
- package/dist/cjs/limits.d.ts.map +1 -1
- package/dist/cjs/limits.js +65 -10
- package/dist/cjs/limits.js.map +1 -1
- package/dist/cjs/middleware/common.d.ts +24 -2
- package/dist/cjs/middleware/common.d.ts.map +1 -1
- package/dist/cjs/middleware/common.js +66 -4
- package/dist/cjs/middleware/common.js.map +1 -1
- package/dist/cjs/middleware/express.d.ts +11 -6
- package/dist/cjs/middleware/express.d.ts.map +1 -1
- package/dist/cjs/middleware/express.js +25 -62
- package/dist/cjs/middleware/express.js.map +1 -1
- package/dist/cjs/middleware/fastify.d.ts +9 -5
- package/dist/cjs/middleware/fastify.d.ts.map +1 -1
- package/dist/cjs/middleware/fastify.js +28 -48
- package/dist/cjs/middleware/fastify.js.map +1 -1
- package/dist/cjs/middleware/koa.d.ts +11 -6
- package/dist/cjs/middleware/koa.d.ts.map +1 -1
- package/dist/cjs/middleware/koa.js +28 -46
- package/dist/cjs/middleware/koa.js.map +1 -1
- package/dist/cjs/transport-core/core.d.ts +355 -0
- package/dist/cjs/transport-core/core.d.ts.map +1 -0
- package/dist/cjs/transport-core/core.js +1047 -0
- package/dist/cjs/transport-core/core.js.map +1 -0
- package/dist/cjs/types.d.ts +108 -20
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/esm/client.js +191 -145
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/idempotency.js +9 -9
- package/dist/esm/index.js +7 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/limits.js +62 -10
- package/dist/esm/limits.js.map +1 -1
- package/dist/esm/middleware/common.js +62 -4
- package/dist/esm/middleware/common.js.map +1 -1
- package/dist/esm/middleware/express.js +24 -61
- package/dist/esm/middleware/express.js.map +1 -1
- package/dist/esm/middleware/fastify.js +27 -47
- package/dist/esm/middleware/fastify.js.map +1 -1
- package/dist/esm/middleware/koa.js +27 -45
- package/dist/esm/middleware/koa.js.map +1 -1
- package/dist/esm/package.json +1 -0
- package/dist/esm/transport-core/core.js +1038 -0
- package/dist/esm/transport-core/core.js.map +1 -0
- package/dist/types/client.d.ts +33 -35
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/idempotency.d.ts +9 -9
- package/dist/types/index.d.ts +8 -8
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/limits.d.ts +47 -7
- package/dist/types/limits.d.ts.map +1 -1
- package/dist/types/middleware/common.d.ts +24 -2
- package/dist/types/middleware/common.d.ts.map +1 -1
- package/dist/types/middleware/express.d.ts +11 -6
- package/dist/types/middleware/express.d.ts.map +1 -1
- package/dist/types/middleware/fastify.d.ts +9 -5
- package/dist/types/middleware/fastify.d.ts.map +1 -1
- package/dist/types/middleware/koa.d.ts +11 -6
- package/dist/types/middleware/koa.d.ts.map +1 -1
- package/dist/types/transport-core/core.d.ts +355 -0
- package/dist/types/transport-core/core.d.ts.map +1 -0
- package/dist/types/types.d.ts +108 -20
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +22 -6
- package/dist/cjs/buffer.d.ts +0 -27
- package/dist/cjs/buffer.d.ts.map +0 -1
- package/dist/cjs/buffer.js +0 -75
- package/dist/cjs/buffer.js.map +0 -1
- package/dist/cjs/transport.d.ts +0 -39
- package/dist/cjs/transport.d.ts.map +0 -1
- package/dist/cjs/transport.js +0 -116
- package/dist/cjs/transport.js.map +0 -1
- package/dist/esm/buffer.js +0 -71
- package/dist/esm/buffer.js.map +0 -1
- package/dist/esm/transport.js +0 -112
- package/dist/esm/transport.js.map +0 -1
- package/dist/types/buffer.d.ts +0 -27
- package/dist/types/buffer.d.ts.map +0 -1
- package/dist/types/transport.d.ts +0 -39
- package/dist/types/transport.d.ts.map +0 -1
package/dist/cjs/limits.d.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* billed. The server reports it per event (`errors[]` in the batch response,
|
|
6
6
|
* indexed), so the rest of the batch survives — but the SDK sends in the
|
|
7
7
|
* background, so that report reaches nobody: the usage is simply gone. Checking
|
|
8
|
-
* the same limits at `track()`
|
|
9
|
-
*
|
|
8
|
+
* the same limits at `track()` reports the problem where it happens: the event
|
|
9
|
+
* is not buffered or sent, it is counted in `droppedCount`, WARN-logged with
|
|
10
|
+
* the field, the limit and the offending value, and handed to the opt-in
|
|
11
|
+
* `onDrop` hook with reason `'invalid'`. `track()` does not throw for it.
|
|
10
12
|
*
|
|
11
13
|
* Source: `dto/IngestUsageEventRequest` (aforo-nextgen-usage-ingestor-service) —
|
|
12
14
|
* the `@Size` and `@Digits` bean constraints, which are compiled into the
|
|
@@ -21,9 +23,15 @@
|
|
|
21
23
|
* Only `occurredAt`'s shape is checked, since a malformed timestamp is invalid
|
|
22
24
|
* under every configuration.
|
|
23
25
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* offending value.
|
|
26
|
+
* The limit check never truncates or rounds a value the caller set — that would
|
|
27
|
+
* change what is billed. The offending event is dropped instead (reason
|
|
28
|
+
* `'invalid'`), naming the field, the limit, and the offending value.
|
|
29
|
+
*
|
|
30
|
+
* The one exception is a label the SDK itself reads off an incoming request
|
|
31
|
+
* (the middlewares' `endpointPath` and `httpMethod`): dropping there would let
|
|
32
|
+
* an API consumer avoid metering by sending an over-long path. Those are cut to
|
|
33
|
+
* the limit where they are derived (`truncateRequestLabel`) and the event is
|
|
34
|
+
* still sent.
|
|
27
35
|
*/
|
|
28
36
|
/** Maximum characters the ingestor accepts per string field (`@Size(max = ...)`). */
|
|
29
37
|
export declare const MAX_LENGTHS: {
|
|
@@ -36,12 +44,35 @@ export declare const MAX_LENGTHS: {
|
|
|
36
44
|
readonly sessionId: 64;
|
|
37
45
|
readonly agentId: 36;
|
|
38
46
|
readonly toolName: 64;
|
|
47
|
+
readonly capabilityName: 64;
|
|
39
48
|
readonly endpointPath: 512;
|
|
40
49
|
readonly httpMethod: 16;
|
|
50
|
+
readonly subscriptionId: 64;
|
|
51
|
+
readonly grpcService: 255;
|
|
52
|
+
readonly grpcMethod: 128;
|
|
53
|
+
readonly gqlOperationName: 255;
|
|
54
|
+
readonly wsConnectionId: 64;
|
|
55
|
+
readonly mqttTopic: 500;
|
|
56
|
+
readonly mqttClientId: 128;
|
|
41
57
|
};
|
|
42
58
|
/** `@Digits(integer = 14, fraction = 6)` — `usage_events.quantity` is NUMERIC(20,6). */
|
|
43
59
|
export declare const MAX_QUANTITY_INTEGER_DIGITS = 14;
|
|
44
60
|
export declare const MAX_QUANTITY_DECIMAL_PLACES = 6;
|
|
61
|
+
/** Fields whose length the ingestor caps, checked when the caller sets them. */
|
|
62
|
+
type LengthCheckedField = keyof typeof MAX_LENGTHS;
|
|
63
|
+
/**
|
|
64
|
+
* Cut `value` to at most `max` UTF-16 code units — how the ingestor counts —
|
|
65
|
+
* without leaving half a surrogate pair at the end.
|
|
66
|
+
*/
|
|
67
|
+
export declare function truncateToLimit(value: string, max: number): string;
|
|
68
|
+
/**
|
|
69
|
+
* Bound a label the SDK derived from an incoming request to the ingestor's
|
|
70
|
+
* limit for `field`. Logs one WARN per field per process. Not for values the
|
|
71
|
+
* SDK caller sets: those are never altered (an over-long one drops the event).
|
|
72
|
+
*/
|
|
73
|
+
export declare function truncateRequestLabel(field: LengthCheckedField, value: string): string;
|
|
74
|
+
/** Test hook: forget which labels were already reported as truncated. */
|
|
75
|
+
export declare function resetTruncationWarnings(): void;
|
|
45
76
|
/**
|
|
46
77
|
* Integer and decimal digit counts as the ingestor's `@Digits` sees them, i.e.
|
|
47
78
|
* as `BigDecimal` reads the number the SDK puts on the wire:
|
|
@@ -70,11 +101,20 @@ export interface LimitCheckedEvent {
|
|
|
70
101
|
sessionId?: unknown;
|
|
71
102
|
agentId?: unknown;
|
|
72
103
|
toolName?: unknown;
|
|
104
|
+
capabilityName?: unknown;
|
|
105
|
+
subscriptionId?: unknown;
|
|
106
|
+
grpcService?: unknown;
|
|
107
|
+
grpcMethod?: unknown;
|
|
108
|
+
gqlOperationName?: unknown;
|
|
109
|
+
wsConnectionId?: unknown;
|
|
110
|
+
mqttTopic?: unknown;
|
|
111
|
+
mqttClientId?: unknown;
|
|
73
112
|
}
|
|
74
113
|
/**
|
|
75
114
|
* Describe the first ingestor constraint this event breaks, or `null` when it
|
|
76
|
-
* would be accepted.
|
|
77
|
-
*
|
|
115
|
+
* would be accepted. `AforoClient.track()` uses it to drop an invalid event
|
|
116
|
+
* (reason `'invalid'`) before it is buffered.
|
|
78
117
|
*/
|
|
79
118
|
export declare function describeLimitViolation(event: LimitCheckedEvent): string | null;
|
|
119
|
+
export {};
|
|
80
120
|
//# sourceMappingURL=limits.d.ts.map
|
package/dist/cjs/limits.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/limits.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,qFAAqF;AACrF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;CAoBd,CAAC;AAEX,wFAAwF;AACxF,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAC9C,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAc7C,gFAAgF;AAChF,KAAK,kBAAkB,GAAG,MAAM,OAAO,WAAW,CAAC;AAUnD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAOlE;AAKD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAWrF;AAED,yEAAyE;AACzE,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAaD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAYnG;AA+BD,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CAe9E"}
|
package/dist/cjs/limits.js
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* billed. The server reports it per event (`errors[]` in the batch response,
|
|
7
7
|
* indexed), so the rest of the batch survives — but the SDK sends in the
|
|
8
8
|
* background, so that report reaches nobody: the usage is simply gone. Checking
|
|
9
|
-
* the same limits at `track()`
|
|
10
|
-
*
|
|
9
|
+
* the same limits at `track()` reports the problem where it happens: the event
|
|
10
|
+
* is not buffered or sent, it is counted in `droppedCount`, WARN-logged with
|
|
11
|
+
* the field, the limit and the offending value, and handed to the opt-in
|
|
12
|
+
* `onDrop` hook with reason `'invalid'`. `track()` does not throw for it.
|
|
11
13
|
*
|
|
12
14
|
* Source: `dto/IngestUsageEventRequest` (aforo-nextgen-usage-ingestor-service) —
|
|
13
15
|
* the `@Size` and `@Digits` bean constraints, which are compiled into the
|
|
@@ -22,12 +24,21 @@
|
|
|
22
24
|
* Only `occurredAt`'s shape is checked, since a malformed timestamp is invalid
|
|
23
25
|
* under every configuration.
|
|
24
26
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* offending value.
|
|
27
|
+
* The limit check never truncates or rounds a value the caller set — that would
|
|
28
|
+
* change what is billed. The offending event is dropped instead (reason
|
|
29
|
+
* `'invalid'`), naming the field, the limit, and the offending value.
|
|
30
|
+
*
|
|
31
|
+
* The one exception is a label the SDK itself reads off an incoming request
|
|
32
|
+
* (the middlewares' `endpointPath` and `httpMethod`): dropping there would let
|
|
33
|
+
* an API consumer avoid metering by sending an over-long path. Those are cut to
|
|
34
|
+
* the limit where they are derived (`truncateRequestLabel`) and the event is
|
|
35
|
+
* still sent.
|
|
28
36
|
*/
|
|
29
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
38
|
exports.MAX_QUANTITY_DECIMAL_PLACES = exports.MAX_QUANTITY_INTEGER_DIGITS = exports.MAX_LENGTHS = void 0;
|
|
39
|
+
exports.truncateToLimit = truncateToLimit;
|
|
40
|
+
exports.truncateRequestLabel = truncateRequestLabel;
|
|
41
|
+
exports.resetTruncationWarnings = resetTruncationWarnings;
|
|
31
42
|
exports.quantityDigits = quantityDigits;
|
|
32
43
|
exports.describeLimitViolation = describeLimitViolation;
|
|
33
44
|
/** Maximum characters the ingestor accepts per string field (`@Size(max = ...)`). */
|
|
@@ -41,8 +52,16 @@ exports.MAX_LENGTHS = {
|
|
|
41
52
|
sessionId: 64,
|
|
42
53
|
agentId: 36,
|
|
43
54
|
toolName: 64,
|
|
55
|
+
capabilityName: 64,
|
|
44
56
|
endpointPath: 512,
|
|
45
57
|
httpMethod: 16,
|
|
58
|
+
subscriptionId: 64,
|
|
59
|
+
grpcService: 255,
|
|
60
|
+
grpcMethod: 128,
|
|
61
|
+
gqlOperationName: 255,
|
|
62
|
+
wsConnectionId: 64,
|
|
63
|
+
mqttTopic: 500,
|
|
64
|
+
mqttClientId: 128,
|
|
46
65
|
};
|
|
47
66
|
/** `@Digits(integer = 14, fraction = 6)` — `usage_events.quantity` is NUMERIC(20,6). */
|
|
48
67
|
exports.MAX_QUANTITY_INTEGER_DIGITS = 14;
|
|
@@ -61,6 +80,42 @@ const ISO_DATE_TIME = /^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?([Z
|
|
|
61
80
|
function lengthOf(value) {
|
|
62
81
|
return value.length;
|
|
63
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Cut `value` to at most `max` UTF-16 code units — how the ingestor counts —
|
|
85
|
+
* without leaving half a surrogate pair at the end.
|
|
86
|
+
*/
|
|
87
|
+
function truncateToLimit(value, max) {
|
|
88
|
+
if (value.length <= max)
|
|
89
|
+
return value;
|
|
90
|
+
let end = Math.max(0, max);
|
|
91
|
+
const last = end > 0 ? value.charCodeAt(end - 1) : 0;
|
|
92
|
+
// A high surrogate at the cut means its low half was cut off: drop it too.
|
|
93
|
+
if (last >= 0xd800 && last <= 0xdbff)
|
|
94
|
+
end -= 1;
|
|
95
|
+
return value.slice(0, end);
|
|
96
|
+
}
|
|
97
|
+
/** Labels already reported as truncated — one WARN per label name per process. */
|
|
98
|
+
const truncationWarned = new Set();
|
|
99
|
+
/**
|
|
100
|
+
* Bound a label the SDK derived from an incoming request to the ingestor's
|
|
101
|
+
* limit for `field`. Logs one WARN per field per process. Not for values the
|
|
102
|
+
* SDK caller sets: those are never altered (an over-long one drops the event).
|
|
103
|
+
*/
|
|
104
|
+
function truncateRequestLabel(field, value) {
|
|
105
|
+
const max = exports.MAX_LENGTHS[field];
|
|
106
|
+
if (typeof value !== 'string' || value.length <= max)
|
|
107
|
+
return value;
|
|
108
|
+
if (!truncationWarned.has(field)) {
|
|
109
|
+
truncationWarned.add(field);
|
|
110
|
+
console.warn(`[aforo] ${field} taken from the request was longer than the ingestor's limit and was `
|
|
111
|
+
+ `truncated to ${max} characters; the event is still sent. Logged once per label.`);
|
|
112
|
+
}
|
|
113
|
+
return truncateToLimit(value, max);
|
|
114
|
+
}
|
|
115
|
+
/** Test hook: forget which labels were already reported as truncated. */
|
|
116
|
+
function resetTruncationWarnings() {
|
|
117
|
+
truncationWarned.clear();
|
|
118
|
+
}
|
|
64
119
|
function lengthError(field, value) {
|
|
65
120
|
if (value === undefined || value === null)
|
|
66
121
|
return null;
|
|
@@ -70,7 +125,7 @@ function lengthError(field, value) {
|
|
|
70
125
|
if (len <= max)
|
|
71
126
|
return null;
|
|
72
127
|
return `${field} is ${len} characters, exceeding the ingestor's ${max}-character limit `
|
|
73
|
-
+ `(value starts "${text.slice(0,
|
|
128
|
+
+ `(value starts "${text.slice(0, 80)}"). Shorten it — the SDK will not truncate it, `
|
|
74
129
|
+ 'because a truncated id bills the wrong thing.';
|
|
75
130
|
}
|
|
76
131
|
/**
|
|
@@ -112,18 +167,18 @@ function quantityError(quantity) {
|
|
|
112
167
|
}
|
|
113
168
|
function occurredAtError(occurredAt) {
|
|
114
169
|
if (!ISO_DATE_TIME.test(occurredAt)) {
|
|
115
|
-
return `occurredAt "${occurredAt}" is not an ISO-8601 timestamp `
|
|
170
|
+
return `occurredAt "${String(occurredAt).slice(0, 80)}" is not an ISO-8601 timestamp `
|
|
116
171
|
+ '(expected e.g. "2026-03-01T14:30:00Z").';
|
|
117
172
|
}
|
|
118
173
|
if (Number.isNaN(Date.parse(occurredAt))) {
|
|
119
|
-
return `occurredAt "${occurredAt}" is not a valid date.`;
|
|
174
|
+
return `occurredAt "${String(occurredAt).slice(0, 80)}" is not a valid date.`;
|
|
120
175
|
}
|
|
121
176
|
return null;
|
|
122
177
|
}
|
|
123
178
|
/**
|
|
124
179
|
* Describe the first ingestor constraint this event breaks, or `null` when it
|
|
125
|
-
* would be accepted.
|
|
126
|
-
*
|
|
180
|
+
* would be accepted. `AforoClient.track()` uses it to drop an invalid event
|
|
181
|
+
* (reason `'invalid'`) before it is buffered.
|
|
127
182
|
*/
|
|
128
183
|
function describeLimitViolation(event) {
|
|
129
184
|
if (event.idempotencyKey !== undefined && event.idempotencyKey !== null
|
package/dist/cjs/limits.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/limits.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/limits.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { MiddlewareOptions } from '../types';
|
|
1
|
+
import { MiddlewareOptions } from '../types.js';
|
|
2
|
+
import { AforoClient } from '../client.js';
|
|
2
3
|
/**
|
|
3
4
|
* Metric recorded for each request when `metricName` is not configured.
|
|
4
5
|
*
|
|
@@ -15,8 +16,29 @@ export declare const DEFAULT_METRIC_NAME = "api_calls";
|
|
|
15
16
|
export declare function isPreflight(method: string | undefined): boolean;
|
|
16
17
|
/** Resolve the metric: resolver function, then fixed name, then DEFAULT_METRIC_NAME. */
|
|
17
18
|
export declare function resolveMetricName(options: MiddlewareOptions, req: any, res: any): string;
|
|
18
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Request path without its query string. Read off the incoming request, so an
|
|
21
|
+
* over-long path is cut to the ingestor's limit (512) instead of dropping the
|
|
22
|
+
* event — otherwise a caller could avoid metering with a long URL.
|
|
23
|
+
*/
|
|
19
24
|
export declare function endpointPathOf(url: string): string;
|
|
25
|
+
/** HTTP method off the incoming request, cut to the ingestor's limit (16). */
|
|
26
|
+
export declare function httpMethodOf(method: string): string;
|
|
20
27
|
/** First non-empty string among the candidates, or null. */
|
|
21
28
|
export declare function firstNonEmpty(...values: unknown[]): string | null;
|
|
29
|
+
/** The client a middleware meters through: the app's own, or one built from the options. */
|
|
30
|
+
export declare function middlewareClient(options: MiddlewareOptions): AforoClient;
|
|
31
|
+
/**
|
|
32
|
+
* Build the per-request customer lookup. Throws at setup when neither a
|
|
33
|
+
* resolver nor the explicit `trustCustomerHeader` opt-in is configured: the
|
|
34
|
+
* X-Customer-Id header is set by whoever calls the API, so it is never read
|
|
35
|
+
* by default.
|
|
36
|
+
*/
|
|
37
|
+
export declare function customerLookup(options: MiddlewareOptions, readHeader: (req: any) => unknown): (req: any, headerSource?: any) => string | null;
|
|
38
|
+
/**
|
|
39
|
+
* The endpointPath to send: the query string removed, dynamic segments
|
|
40
|
+
* collapsed unless `normalizePath` is false (a framework route template wins),
|
|
41
|
+
* and cut to the ingestor's 512-character limit.
|
|
42
|
+
*/
|
|
43
|
+
export declare function endpointPathFor(options: MiddlewareOptions, rawPath: string, req: any, routeTemplate?: unknown): string;
|
|
22
44
|
//# sourceMappingURL=common.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../src/middleware/common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../src/middleware/common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3C;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAE/C;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE/D;AAED,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,MAAM,CAKxF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,4DAA4D;AAC5D,wBAAgB,aAAa,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,GAAG,IAAI,CAOjE;AAED,4FAA4F;AAC5F,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,WAAW,CAMxE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,GAChC,CAAC,GAAG,EAAE,GAAG,EAAE,YAAY,CAAC,EAAE,GAAG,KAAK,MAAM,GAAG,IAAI,CAkBjD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAUtH"}
|
|
@@ -4,7 +4,14 @@ exports.DEFAULT_METRIC_NAME = void 0;
|
|
|
4
4
|
exports.isPreflight = isPreflight;
|
|
5
5
|
exports.resolveMetricName = resolveMetricName;
|
|
6
6
|
exports.endpointPathOf = endpointPathOf;
|
|
7
|
+
exports.httpMethodOf = httpMethodOf;
|
|
7
8
|
exports.firstNonEmpty = firstNonEmpty;
|
|
9
|
+
exports.middlewareClient = middlewareClient;
|
|
10
|
+
exports.customerLookup = customerLookup;
|
|
11
|
+
exports.endpointPathFor = endpointPathFor;
|
|
12
|
+
const limits_js_1 = require("../limits.js");
|
|
13
|
+
const client_js_1 = require("../client.js");
|
|
14
|
+
const path_normalizer_js_1 = require("../path-normalizer.js");
|
|
8
15
|
/**
|
|
9
16
|
* Metric recorded for each request when `metricName` is not configured.
|
|
10
17
|
*
|
|
@@ -28,11 +35,17 @@ function resolveMetricName(options, req, res) {
|
|
|
28
35
|
}
|
|
29
36
|
return options.metricName || exports.DEFAULT_METRIC_NAME;
|
|
30
37
|
}
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Request path without its query string. Read off the incoming request, so an
|
|
40
|
+
* over-long path is cut to the ingestor's limit (512) instead of dropping the
|
|
41
|
+
* event — otherwise a caller could avoid metering with a long URL.
|
|
42
|
+
*/
|
|
34
43
|
function endpointPathOf(url) {
|
|
35
|
-
return (url.split('?')[0] || '/')
|
|
44
|
+
return (0, limits_js_1.truncateRequestLabel)('endpointPath', String(url).split('?')[0] || '/');
|
|
45
|
+
}
|
|
46
|
+
/** HTTP method off the incoming request, cut to the ingestor's limit (16). */
|
|
47
|
+
function httpMethodOf(method) {
|
|
48
|
+
return (0, limits_js_1.truncateRequestLabel)('httpMethod', String(method));
|
|
36
49
|
}
|
|
37
50
|
/** First non-empty string among the candidates, or null. */
|
|
38
51
|
function firstNonEmpty(...values) {
|
|
@@ -45,4 +58,53 @@ function firstNonEmpty(...values) {
|
|
|
45
58
|
}
|
|
46
59
|
return null;
|
|
47
60
|
}
|
|
61
|
+
/** The client a middleware meters through: the app's own, or one built from the options. */
|
|
62
|
+
function middlewareClient(options) {
|
|
63
|
+
if (options?.client)
|
|
64
|
+
return options.client;
|
|
65
|
+
if (!options?.apiKey) {
|
|
66
|
+
throw new Error('Aforo middleware needs either `client` (an AforoClient your app owns) or `apiKey`.');
|
|
67
|
+
}
|
|
68
|
+
return new client_js_1.AforoClient({ ...(options.clientOptions ?? {}), apiKey: options.apiKey, baseUrl: options.baseUrl });
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Build the per-request customer lookup. Throws at setup when neither a
|
|
72
|
+
* resolver nor the explicit `trustCustomerHeader` opt-in is configured: the
|
|
73
|
+
* X-Customer-Id header is set by whoever calls the API, so it is never read
|
|
74
|
+
* by default.
|
|
75
|
+
*/
|
|
76
|
+
function customerLookup(options, readHeader) {
|
|
77
|
+
const legacy = options?.customerId;
|
|
78
|
+
const resolver = options?.customerResolver ??
|
|
79
|
+
(typeof legacy === 'function' ? legacy : typeof legacy === 'string' && legacy.trim() ? () => legacy : undefined);
|
|
80
|
+
const trustHeader = options?.trustCustomerHeader === true;
|
|
81
|
+
if (!resolver && !trustHeader) {
|
|
82
|
+
throw new Error('Aforo middleware needs a customerResolver: (req) => the Aforo customer id from identity your app ' +
|
|
83
|
+
'authenticated (session, verified JWT subject, API-key lookup). To bill the X-Customer-Id header ' +
|
|
84
|
+
'instead — only safe behind a proxy that sets it — pass trustCustomerHeader: true.');
|
|
85
|
+
}
|
|
86
|
+
return (req, headerSource = req) => {
|
|
87
|
+
const fromResolver = resolver ? firstNonEmpty(resolver(req)) : null;
|
|
88
|
+
if (fromResolver)
|
|
89
|
+
return fromResolver;
|
|
90
|
+
return trustHeader ? firstNonEmpty(readHeader(headerSource)) : null;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The endpointPath to send: the query string removed, dynamic segments
|
|
95
|
+
* collapsed unless `normalizePath` is false (a framework route template wins),
|
|
96
|
+
* and cut to the ingestor's 512-character limit.
|
|
97
|
+
*/
|
|
98
|
+
function endpointPathFor(options, rawPath, req, routeTemplate) {
|
|
99
|
+
const path = String(rawPath ?? '/').split('?')[0] || '/';
|
|
100
|
+
const mode = options?.normalizePath ?? true;
|
|
101
|
+
let out = path;
|
|
102
|
+
if (typeof mode === 'function') {
|
|
103
|
+
out = String(mode(path, req) || path);
|
|
104
|
+
}
|
|
105
|
+
else if (mode) {
|
|
106
|
+
out = (0, path_normalizer_js_1.normalizePath)(path, typeof routeTemplate === 'string' && routeTemplate ? routeTemplate : undefined);
|
|
107
|
+
}
|
|
108
|
+
return endpointPathOf(out);
|
|
109
|
+
}
|
|
48
110
|
//# sourceMappingURL=common.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../../src/middleware/common.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../../src/middleware/common.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import { MiddlewareOptions } from '../types';
|
|
1
|
+
import { MiddlewareOptions } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Express middleware that
|
|
3
|
+
* Express middleware that records one usage event per request.
|
|
4
4
|
*
|
|
5
|
-
* Hooks into `res.on('finish')` — runs AFTER the response is sent
|
|
6
|
-
*
|
|
5
|
+
* Hooks into `res.on('finish')` — runs AFTER the response is sent, so it adds
|
|
6
|
+
* no latency to the request. Metering never throws into the request.
|
|
7
7
|
*
|
|
8
8
|
* ```typescript
|
|
9
|
+
* import { AforoClient } from '@aforoai/metering';
|
|
9
10
|
* import { expressMiddleware } from '@aforoai/metering/middleware/express';
|
|
10
|
-
*
|
|
11
|
+
* const aforo = new AforoClient({ apiKey: process.env.AFORO_API_KEY!, productType: 'API' });
|
|
12
|
+
* app.use(expressMiddleware({ client: aforo, customerResolver: (req) => req.user?.aforoCustomerId }));
|
|
13
|
+
* process.once('SIGTERM', async () => { await aforo.shutdown(); process.exit(0); });
|
|
11
14
|
* ```
|
|
12
15
|
*/
|
|
13
|
-
export declare function expressMiddleware(options: MiddlewareOptions): (req: any, res: any, next: any) => void
|
|
16
|
+
export declare function expressMiddleware(options: MiddlewareOptions): ((req: any, res: any, next: any) => void) & {
|
|
17
|
+
client: import("../client.js").AforoClient;
|
|
18
|
+
};
|
|
14
19
|
export declare const middleware: typeof expressMiddleware;
|
|
15
20
|
//# sourceMappingURL=express.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../../src/middleware/express.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../../src/middleware/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKhD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,UAOJ,GAAG,OAAO,GAAG,QAAQ,GAAG;;EAsC/E;AAGD,eAAO,MAAM,UAAU,0BAAoB,CAAC"}
|
|
@@ -2,102 +2,65 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.middleware = void 0;
|
|
4
4
|
exports.expressMiddleware = expressMiddleware;
|
|
5
|
-
const
|
|
6
|
-
const common_1 = require("./common");
|
|
5
|
+
const common_js_1 = require("./common.js");
|
|
7
6
|
const DEFAULT_EXCLUDE_PATHS = ['/health', '/ready', '/metrics', '/favicon.ico'];
|
|
8
7
|
/**
|
|
9
|
-
* Express middleware that
|
|
8
|
+
* Express middleware that records one usage event per request.
|
|
10
9
|
*
|
|
11
|
-
* Hooks into `res.on('finish')` — runs AFTER the response is sent
|
|
12
|
-
*
|
|
10
|
+
* Hooks into `res.on('finish')` — runs AFTER the response is sent, so it adds
|
|
11
|
+
* no latency to the request. Metering never throws into the request.
|
|
13
12
|
*
|
|
14
13
|
* ```typescript
|
|
14
|
+
* import { AforoClient } from '@aforoai/metering';
|
|
15
15
|
* import { expressMiddleware } from '@aforoai/metering/middleware/express';
|
|
16
|
-
*
|
|
16
|
+
* const aforo = new AforoClient({ apiKey: process.env.AFORO_API_KEY!, productType: 'API' });
|
|
17
|
+
* app.use(expressMiddleware({ client: aforo, customerResolver: (req) => req.user?.aforoCustomerId }));
|
|
18
|
+
* process.once('SIGTERM', async () => { await aforo.shutdown(); process.exit(0); });
|
|
17
19
|
* ```
|
|
18
20
|
*/
|
|
19
21
|
function expressMiddleware(options) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
baseUrl: options.baseUrl,
|
|
23
|
-
...options.clientOptions,
|
|
24
|
-
});
|
|
22
|
+
const lookupCustomer = (0, common_js_1.customerLookup)(options, (req) => req.headers?.['x-customer-id']);
|
|
23
|
+
const client = (0, common_js_1.middlewareClient)(options);
|
|
25
24
|
const excludePaths = options.excludePaths ?? DEFAULT_EXCLUDE_PATHS;
|
|
26
25
|
const excludeStatusCodes = options.excludeStatusCodes ?? [];
|
|
27
|
-
|
|
28
|
-
// Capture timing
|
|
26
|
+
const handler = function aforoMeteringMiddleware(req, res, next) {
|
|
29
27
|
const startTime = Date.now();
|
|
30
28
|
res.on('finish', () => {
|
|
31
29
|
try {
|
|
32
30
|
const path = req.originalUrl || req.url || '/';
|
|
33
31
|
const method = req.method || 'UNKNOWN';
|
|
34
32
|
const statusCode = res.statusCode || 0;
|
|
35
|
-
|
|
36
|
-
if ((0, common_1.isPreflight)(method))
|
|
33
|
+
if ((0, common_js_1.isPreflight)(method))
|
|
37
34
|
return;
|
|
38
35
|
if (excludePaths.some((p) => path.startsWith(p)))
|
|
39
36
|
return;
|
|
40
37
|
if (excludeStatusCodes.includes(statusCode))
|
|
41
38
|
return;
|
|
42
|
-
|
|
43
|
-
const metricName = (0, common_1.resolveMetricName)(options, req, res);
|
|
44
|
-
// Resolve quantity
|
|
45
|
-
let quantity;
|
|
46
|
-
if (typeof options.quantity === 'function') {
|
|
47
|
-
quantity = options.quantity(req, res);
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
quantity = options.quantity ?? 1;
|
|
51
|
-
}
|
|
52
|
-
// Resolve customer ID
|
|
53
|
-
let customerId;
|
|
54
|
-
if (typeof options.customerId === 'function') {
|
|
55
|
-
customerId = options.customerId(req);
|
|
56
|
-
}
|
|
57
|
-
else if (options.customerId) {
|
|
58
|
-
customerId = options.customerId;
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
customerId = extractCustomerId(req);
|
|
62
|
-
}
|
|
63
|
-
if (!customerId)
|
|
64
|
-
return; // Can't meter without a customer
|
|
39
|
+
const quantity = typeof options.quantity === 'function' ? options.quantity(req, res) : options.quantity ?? 1;
|
|
65
40
|
if (!(quantity > 0))
|
|
66
41
|
return; // The ingestor rejects quantity <= 0
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (options.metadata) {
|
|
70
|
-
metadata = options.metadata(req, res);
|
|
71
|
-
}
|
|
42
|
+
const route = typeof req.route?.path === 'string' ? `${req.baseUrl ?? ''}${req.route.path}` : undefined;
|
|
43
|
+
// No customer → track() drops it as 'invalid' and counts it; the request is unaffected.
|
|
72
44
|
client.track({
|
|
73
|
-
customerId,
|
|
74
|
-
metricName,
|
|
45
|
+
customerId: lookupCustomer(req) ?? '',
|
|
46
|
+
metricName: (0, common_js_1.resolveMetricName)(options, req, res),
|
|
75
47
|
quantity,
|
|
76
|
-
metadata,
|
|
48
|
+
metadata: options.metadata ? options.metadata(req, res) : undefined,
|
|
77
49
|
productType: options.productType,
|
|
78
|
-
endpointPath: (0,
|
|
79
|
-
httpMethod: method,
|
|
50
|
+
endpointPath: (0, common_js_1.endpointPathFor)(options, path, req, route),
|
|
51
|
+
httpMethod: (0, common_js_1.httpMethodOf)(method),
|
|
80
52
|
statusCode,
|
|
81
53
|
responseTimeMs: Date.now() - startTime,
|
|
82
|
-
}).catch(() => { });
|
|
54
|
+
}).catch((err) => console.debug(`[aforo] metering skipped: ${err?.message ?? err}`));
|
|
83
55
|
}
|
|
84
|
-
catch {
|
|
85
|
-
// Never let metering errors affect the API
|
|
56
|
+
catch (err) {
|
|
57
|
+
// Never let metering errors affect the API.
|
|
58
|
+
console.debug(`[aforo] metering skipped: ${err?.message ?? err}`);
|
|
86
59
|
}
|
|
87
60
|
});
|
|
88
61
|
next();
|
|
89
62
|
};
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Default customer resolution: authenticated user id, then X-Customer-Id.
|
|
93
|
-
*
|
|
94
|
-
* The caller's X-Api-Key header is deliberately NOT used: that is the end
|
|
95
|
-
* user's secret, and using it as customerId wrote credentials into billing data
|
|
96
|
-
* while never matching an Aforo customer. Configure `customerId` to map your
|
|
97
|
-
* callers to Aforo customer ids.
|
|
98
|
-
*/
|
|
99
|
-
function extractCustomerId(req) {
|
|
100
|
-
return (0, common_1.firstNonEmpty)(req.user?.id, req.user?.sub, req.headers?.['x-customer-id']);
|
|
63
|
+
return Object.assign(handler, { client });
|
|
101
64
|
}
|
|
102
65
|
// Also export as `middleware` for convenience
|
|
103
66
|
exports.middleware = expressMiddleware;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../../src/middleware/express.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../../src/middleware/express.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { MiddlewareOptions } from '../types';
|
|
1
|
+
import { MiddlewareOptions } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Fastify plugin that
|
|
4
|
-
*
|
|
5
|
-
* Uses the `onResponse` hook — runs AFTER the response is sent.
|
|
3
|
+
* Fastify plugin that records one usage event per request in the
|
|
4
|
+
* `onResponse` hook (after the response is sent).
|
|
6
5
|
*
|
|
7
6
|
* ```typescript
|
|
7
|
+
* import { AforoClient } from '@aforoai/metering';
|
|
8
8
|
* import { fastifyPlugin } from '@aforoai/metering/middleware/fastify';
|
|
9
|
-
*
|
|
9
|
+
* const aforo = new AforoClient({ apiKey: process.env.AFORO_API_KEY!, productType: 'API' });
|
|
10
|
+
* fastify.register(fastifyPlugin, { client: aforo, customerResolver: (request) => request.user?.aforoCustomerId });
|
|
10
11
|
* ```
|
|
12
|
+
*
|
|
13
|
+
* When the plugin creates its own client (apiKey instead of client), it shuts
|
|
14
|
+
* that client down in Fastify's onClose hook. A client you pass in is yours to shut down.
|
|
11
15
|
*/
|
|
12
16
|
export declare function fastifyPlugin(fastify: any, options: MiddlewareOptions): Promise<void>;
|
|
13
17
|
//# sourceMappingURL=fastify.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../../../src/middleware/fastify.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../../../src/middleware/fastify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKhD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,iBAAiB,iBAiD3E"}
|