@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
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.fastifyPlugin = fastifyPlugin;
|
|
4
|
-
const
|
|
5
|
-
const common_1 = require("./common");
|
|
4
|
+
const common_js_1 = require("./common.js");
|
|
6
5
|
const DEFAULT_EXCLUDE_PATHS = ['/health', '/ready', '/metrics', '/favicon.ico'];
|
|
7
6
|
/**
|
|
8
|
-
* Fastify plugin that
|
|
9
|
-
*
|
|
10
|
-
* Uses the `onResponse` hook — runs AFTER the response is sent.
|
|
7
|
+
* Fastify plugin that records one usage event per request in the
|
|
8
|
+
* `onResponse` hook (after the response is sent).
|
|
11
9
|
*
|
|
12
10
|
* ```typescript
|
|
11
|
+
* import { AforoClient } from '@aforoai/metering';
|
|
13
12
|
* import { fastifyPlugin } from '@aforoai/metering/middleware/fastify';
|
|
14
|
-
*
|
|
13
|
+
* const aforo = new AforoClient({ apiKey: process.env.AFORO_API_KEY!, productType: 'API' });
|
|
14
|
+
* fastify.register(fastifyPlugin, { client: aforo, customerResolver: (request) => request.user?.aforoCustomerId });
|
|
15
15
|
* ```
|
|
16
|
+
*
|
|
17
|
+
* When the plugin creates its own client (apiKey instead of client), it shuts
|
|
18
|
+
* that client down in Fastify's onClose hook. A client you pass in is yours to shut down.
|
|
16
19
|
*/
|
|
17
20
|
async function fastifyPlugin(fastify, options) {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
...options.clientOptions,
|
|
22
|
-
});
|
|
21
|
+
const lookupCustomer = (0, common_js_1.customerLookup)(options, (request) => request.headers?.['x-customer-id']);
|
|
22
|
+
const owned = !options.client;
|
|
23
|
+
const client = (0, common_js_1.middlewareClient)(options);
|
|
23
24
|
const excludePaths = options.excludePaths ?? DEFAULT_EXCLUDE_PATHS;
|
|
24
25
|
const excludeStatusCodes = options.excludeStatusCodes ?? [];
|
|
25
26
|
fastify.addHook('onResponse', (request, reply, done) => {
|
|
@@ -27,62 +28,41 @@ async function fastifyPlugin(fastify, options) {
|
|
|
27
28
|
const path = request.url || '/';
|
|
28
29
|
const method = request.method || 'UNKNOWN';
|
|
29
30
|
const statusCode = reply.statusCode || 0;
|
|
30
|
-
if ((0,
|
|
31
|
+
if ((0, common_js_1.isPreflight)(method))
|
|
31
32
|
return done();
|
|
32
33
|
if (excludePaths.some((p) => path.startsWith(p)))
|
|
33
34
|
return done();
|
|
34
35
|
if (excludeStatusCodes.includes(statusCode))
|
|
35
36
|
return done();
|
|
36
|
-
const
|
|
37
|
-
let quantity;
|
|
38
|
-
if (typeof options.quantity === 'function') {
|
|
39
|
-
quantity = options.quantity(request, reply);
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
quantity = options.quantity ?? 1;
|
|
43
|
-
}
|
|
44
|
-
let customerId;
|
|
45
|
-
if (typeof options.customerId === 'function') {
|
|
46
|
-
customerId = options.customerId(request);
|
|
47
|
-
}
|
|
48
|
-
else if (options.customerId) {
|
|
49
|
-
customerId = options.customerId;
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
// Never the caller's X-Api-Key: that is a secret, not a customer id.
|
|
53
|
-
customerId = (0, common_1.firstNonEmpty)(request.user?.id, request.headers?.['x-customer-id']);
|
|
54
|
-
}
|
|
55
|
-
if (!customerId)
|
|
56
|
-
return done();
|
|
37
|
+
const quantity = typeof options.quantity === 'function' ? options.quantity(request, reply) : options.quantity ?? 1;
|
|
57
38
|
if (!(quantity > 0))
|
|
58
39
|
return done(); // The ingestor rejects quantity <= 0
|
|
59
|
-
let metadata;
|
|
60
|
-
if (options.metadata) {
|
|
61
|
-
metadata = options.metadata(request, reply);
|
|
62
|
-
}
|
|
63
40
|
const elapsed = typeof reply.elapsedTime === 'number'
|
|
64
41
|
? reply.elapsedTime
|
|
65
42
|
: typeof reply.getResponseTime === 'function' ? reply.getResponseTime() : undefined;
|
|
66
43
|
client.track({
|
|
67
|
-
customerId,
|
|
68
|
-
metricName,
|
|
44
|
+
customerId: lookupCustomer(request) ?? '',
|
|
45
|
+
metricName: (0, common_js_1.resolveMetricName)(options, request, reply),
|
|
69
46
|
quantity,
|
|
70
|
-
metadata,
|
|
47
|
+
metadata: options.metadata ? options.metadata(request, reply) : undefined,
|
|
71
48
|
productType: options.productType,
|
|
72
|
-
endpointPath: (0,
|
|
73
|
-
httpMethod: method,
|
|
49
|
+
endpointPath: (0, common_js_1.endpointPathFor)(options, path, request, request.routeOptions?.url ?? request.routerPath),
|
|
50
|
+
httpMethod: (0, common_js_1.httpMethodOf)(method),
|
|
74
51
|
statusCode,
|
|
75
52
|
...(typeof elapsed === 'number' ? { responseTimeMs: Math.round(elapsed) } : {}),
|
|
76
|
-
}).catch(() => { });
|
|
53
|
+
}).catch((err) => console.debug(`[aforo] metering skipped: ${err?.message ?? err}`));
|
|
77
54
|
}
|
|
78
|
-
catch {
|
|
79
|
-
// Never let metering affect the API
|
|
55
|
+
catch (err) {
|
|
56
|
+
// Never let metering affect the API.
|
|
57
|
+
console.debug(`[aforo] metering skipped: ${err?.message ?? err}`);
|
|
80
58
|
}
|
|
81
59
|
done();
|
|
82
60
|
});
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
61
|
+
if (owned) {
|
|
62
|
+
fastify.addHook('onClose', async () => {
|
|
63
|
+
await client.shutdown();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
86
66
|
}
|
|
87
67
|
// Mark as fastify plugin
|
|
88
68
|
fastifyPlugin[Symbol.for('skip-override')] = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.js","sourceRoot":"","sources":["../../../src/middleware/fastify.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"fastify.js","sourceRoot":"","sources":["../../../src/middleware/fastify.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import { MiddlewareOptions } from '../types';
|
|
1
|
+
import { MiddlewareOptions } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Koa middleware that
|
|
4
|
-
*
|
|
5
|
-
* Captures after `await next()` completes — runs after the response is generated.
|
|
3
|
+
* Koa middleware that records one usage event per request, after `await next()`.
|
|
4
|
+
* Metering never throws into the request.
|
|
6
5
|
*
|
|
7
6
|
* ```typescript
|
|
7
|
+
* import { AforoClient } from '@aforoai/metering';
|
|
8
8
|
* import { koaMiddleware } from '@aforoai/metering/middleware/koa';
|
|
9
|
-
*
|
|
9
|
+
* const aforo = new AforoClient({ apiKey: process.env.AFORO_API_KEY!, productType: 'API' });
|
|
10
|
+
* app.use(koaMiddleware({ client: aforo, customerResolver: (ctx) => ctx.state.user?.aforoCustomerId }));
|
|
10
11
|
* ```
|
|
12
|
+
*
|
|
13
|
+
* `customerResolver` receives the Koa context.
|
|
11
14
|
*/
|
|
12
|
-
export declare function koaMiddleware(options: MiddlewareOptions): (ctx: any, next: any) => Promise<void
|
|
15
|
+
export declare function koaMiddleware(options: MiddlewareOptions): ((ctx: any, next: any) => Promise<void>) & {
|
|
16
|
+
client: import("../client.js").AforoClient;
|
|
17
|
+
};
|
|
13
18
|
//# sourceMappingURL=koa.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"koa.d.ts","sourceRoot":"","sources":["../../../src/middleware/koa.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"koa.d.ts","sourceRoot":"","sources":["../../../src/middleware/koa.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKhD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,UAgBM,GAAG,QAAQ,GAAG;;EAkC3E"}
|
|
@@ -1,82 +1,64 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.koaMiddleware = koaMiddleware;
|
|
4
|
-
const
|
|
5
|
-
const common_1 = require("./common");
|
|
4
|
+
const common_js_1 = require("./common.js");
|
|
6
5
|
const DEFAULT_EXCLUDE_PATHS = ['/health', '/ready', '/metrics', '/favicon.ico'];
|
|
7
6
|
/**
|
|
8
|
-
* Koa middleware that
|
|
9
|
-
*
|
|
10
|
-
* Captures after `await next()` completes — runs after the response is generated.
|
|
7
|
+
* Koa middleware that records one usage event per request, after `await next()`.
|
|
8
|
+
* Metering never throws into the request.
|
|
11
9
|
*
|
|
12
10
|
* ```typescript
|
|
11
|
+
* import { AforoClient } from '@aforoai/metering';
|
|
13
12
|
* import { koaMiddleware } from '@aforoai/metering/middleware/koa';
|
|
14
|
-
*
|
|
13
|
+
* const aforo = new AforoClient({ apiKey: process.env.AFORO_API_KEY!, productType: 'API' });
|
|
14
|
+
* app.use(koaMiddleware({ client: aforo, customerResolver: (ctx) => ctx.state.user?.aforoCustomerId }));
|
|
15
15
|
* ```
|
|
16
|
+
*
|
|
17
|
+
* `customerResolver` receives the Koa context.
|
|
16
18
|
*/
|
|
17
19
|
function koaMiddleware(options) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
...options
|
|
22
|
-
|
|
20
|
+
// customerResolver receives the Koa context; the deprecated customerId function kept receiving ctx.request.
|
|
21
|
+
const legacy = options.customerId;
|
|
22
|
+
const lookupCustomer = (0, common_js_1.customerLookup)({
|
|
23
|
+
...options,
|
|
24
|
+
customerResolver: options.customerResolver ?? (typeof legacy === 'function' ? (ctx) => legacy(ctx?.request) : undefined),
|
|
25
|
+
}, (ctx) => ctx?.get?.('x-customer-id') || ctx?.request?.headers?.['x-customer-id']);
|
|
26
|
+
const client = (0, common_js_1.middlewareClient)(options);
|
|
23
27
|
const excludePaths = options.excludePaths ?? DEFAULT_EXCLUDE_PATHS;
|
|
24
28
|
const excludeStatusCodes = options.excludeStatusCodes ?? [];
|
|
25
|
-
|
|
29
|
+
const handler = async function aforoMeteringMiddleware(ctx, next) {
|
|
26
30
|
const startTime = Date.now();
|
|
27
31
|
await next();
|
|
28
32
|
try {
|
|
29
33
|
const path = ctx.path || ctx.url || '/';
|
|
30
34
|
const method = ctx.method || 'UNKNOWN';
|
|
31
35
|
const statusCode = ctx.status || 0;
|
|
32
|
-
if ((0,
|
|
36
|
+
if ((0, common_js_1.isPreflight)(method))
|
|
33
37
|
return;
|
|
34
38
|
if (excludePaths.some((p) => path.startsWith(p)))
|
|
35
39
|
return;
|
|
36
40
|
if (excludeStatusCodes.includes(statusCode))
|
|
37
41
|
return;
|
|
38
|
-
const
|
|
39
|
-
let quantity;
|
|
40
|
-
if (typeof options.quantity === 'function') {
|
|
41
|
-
quantity = options.quantity(ctx.request, ctx.response);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
quantity = options.quantity ?? 1;
|
|
45
|
-
}
|
|
46
|
-
let customerId;
|
|
47
|
-
if (typeof options.customerId === 'function') {
|
|
48
|
-
customerId = options.customerId(ctx.request);
|
|
49
|
-
}
|
|
50
|
-
else if (options.customerId) {
|
|
51
|
-
customerId = options.customerId;
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
// Never the caller's X-Api-Key: that is a secret, not a customer id.
|
|
55
|
-
customerId = (0, common_1.firstNonEmpty)(ctx.state?.user?.id, ctx.get('x-customer-id'));
|
|
56
|
-
}
|
|
57
|
-
if (!customerId)
|
|
58
|
-
return;
|
|
42
|
+
const quantity = typeof options.quantity === 'function' ? options.quantity(ctx.request, ctx.response) : options.quantity ?? 1;
|
|
59
43
|
if (!(quantity > 0))
|
|
60
44
|
return; // The ingestor rejects quantity <= 0
|
|
61
|
-
let metadata;
|
|
62
|
-
if (options.metadata) {
|
|
63
|
-
metadata = options.metadata(ctx.request, ctx.response);
|
|
64
|
-
}
|
|
65
45
|
client.track({
|
|
66
|
-
customerId,
|
|
67
|
-
metricName,
|
|
46
|
+
customerId: lookupCustomer(ctx) ?? '',
|
|
47
|
+
metricName: (0, common_js_1.resolveMetricName)(options, ctx.request, ctx.response),
|
|
68
48
|
quantity,
|
|
69
|
-
metadata,
|
|
49
|
+
metadata: options.metadata ? options.metadata(ctx.request, ctx.response) : undefined,
|
|
70
50
|
productType: options.productType,
|
|
71
|
-
endpointPath: (0,
|
|
72
|
-
httpMethod: method,
|
|
51
|
+
endpointPath: (0, common_js_1.endpointPathFor)(options, path, ctx, ctx._matchedRoute ?? ctx.routerPath),
|
|
52
|
+
httpMethod: (0, common_js_1.httpMethodOf)(method),
|
|
73
53
|
statusCode,
|
|
74
54
|
responseTimeMs: Date.now() - startTime,
|
|
75
|
-
}).catch(() => { });
|
|
55
|
+
}).catch((err) => console.debug(`[aforo] metering skipped: ${err?.message ?? err}`));
|
|
76
56
|
}
|
|
77
|
-
catch {
|
|
78
|
-
// Never let metering affect the API
|
|
57
|
+
catch (err) {
|
|
58
|
+
// Never let metering affect the API.
|
|
59
|
+
console.debug(`[aforo] metering skipped: ${err?.message ?? err}`);
|
|
79
60
|
}
|
|
80
61
|
};
|
|
62
|
+
return Object.assign(handler, { client });
|
|
81
63
|
}
|
|
82
64
|
//# sourceMappingURL=koa.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"koa.js","sourceRoot":"","sources":["../../../src/middleware/koa.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"koa.js","sourceRoot":"","sources":["../../../src/middleware/koa.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aforo Node transport core (wave 5, contract v2).
|
|
3
|
+
*
|
|
4
|
+
* THE ONE PLACE TO EDIT. This file lives in `node/src/transport-core/` and is
|
|
5
|
+
* copied, byte for byte, into every Node variant (`node-ws`, `node-mqtt`,
|
|
6
|
+
* `node-grpc`, `node-graphql`, `node-mcp`, `node-agent`, `mcp-proxy`) by
|
|
7
|
+
* `scripts/sync-transport.sh node`. Edit it here, then run the sync; CI fails
|
|
8
|
+
* when a copy differs. Normative behaviour: `contract/TRANSPORT-SPEC.md`.
|
|
9
|
+
*
|
|
10
|
+
* Self-contained on purpose: no imports (not even relative ones, so every
|
|
11
|
+
* module format and test runner that receives the copy can load it), only
|
|
12
|
+
* Node built-ins reached through globals (`fetch`, `AbortSignal`, `Buffer`,
|
|
13
|
+
* timers). Target ES2020.
|
|
14
|
+
*
|
|
15
|
+
* What it does, per client:
|
|
16
|
+
* - one bounded FIFO queue (evict-oldest, reported as 'overflow');
|
|
17
|
+
* - one long-lived flusher: `enqueue()` never sends, it only wakes the
|
|
18
|
+
* flusher at `flushCount`; `flush()` hands work to the flusher and waits;
|
|
19
|
+
* at most one ingest request is ever in flight;
|
|
20
|
+
* - full-jitter retries, Retry-After in both forms (capped), a total
|
|
21
|
+
* deadline per request that also covers reading the response body;
|
|
22
|
+
* - redirects are never followed (the API key must not reach another
|
|
23
|
+
* host): a 3xx keeps the batch queued and opens the breaker;
|
|
24
|
+
* - each event is serialized once, at enqueue: an event JSON cannot
|
|
25
|
+
* represent (BigInt, a cycle, NaN) is dropped there as 'invalid';
|
|
26
|
+
* - 413 splits the batch, 2xx per-event `retryable` failures are requeued,
|
|
27
|
+
* other 4xx are dropped as 'rejected';
|
|
28
|
+
* - after the last retry (or on 401/403) the batch goes back to the front
|
|
29
|
+
* of the queue with its keys and a breaker pauses the flusher;
|
|
30
|
+
* - `shutdown()` drains until a deadline and reports leftovers as
|
|
31
|
+
* 'shutdown'; `enqueue()` after close reports 'closed';
|
|
32
|
+
* - no signal handlers: a process `beforeExit` hook flushes within the
|
|
33
|
+
* shutdown deadline. Apps call `shutdown()` from their own SIGTERM handler;
|
|
34
|
+
* - `onAccepted(body, events)` sees the parsed 2xx body of every accepted
|
|
35
|
+
* batch (for fields such as `killedSessionIds`); `onError` receives a
|
|
36
|
+
* `CoreTransportError` whose `kind` says what happened.
|
|
37
|
+
*/
|
|
38
|
+
/** Why events were permanently dropped. See TRANSPORT-SPEC.md "Drop reasons". */
|
|
39
|
+
export type CoreDropReason = 'overflow' | 'rejected' | 'invalid' | 'shutdown' | 'closed' | 'retry_exhausted';
|
|
40
|
+
/**
|
|
41
|
+
* What an onError report is about. `outage` and `auth` keep the events queued
|
|
42
|
+
* (`willRetry` true); the others describe events that are gone.
|
|
43
|
+
* - outage: network errors, timeouts, 408/429/5xx after every retry, or
|
|
44
|
+
* per-event `retryable` failures that never cleared
|
|
45
|
+
* - auth: HTTP 401/403, the API key is wrong or revoked
|
|
46
|
+
* - rejected: the ingestor refused the events (other 4xx, 2xx per-event errors, a single event still 413)
|
|
47
|
+
* - shutdown: still queued when the shutdown deadline passed
|
|
48
|
+
* - closed: tracked after shutdown (reported for the first, then every 1000th)
|
|
49
|
+
*/
|
|
50
|
+
export type CoreErrorKind = 'outage' | 'auth' | 'rejected' | 'shutdown' | 'closed';
|
|
51
|
+
/**
|
|
52
|
+
* The error every onError report receives. `message` is for people; branch on
|
|
53
|
+
* `kind`. `events` are the events concerned: the queued ones for outage/auth,
|
|
54
|
+
* the dropped ones otherwise (empty when the ingestor did not name them).
|
|
55
|
+
*/
|
|
56
|
+
export declare class CoreTransportError<E = CoreEvent> extends Error {
|
|
57
|
+
readonly kind: CoreErrorKind;
|
|
58
|
+
readonly events: E[];
|
|
59
|
+
/** HTTP status of the answer that caused the report, when there was one. */
|
|
60
|
+
readonly status?: number;
|
|
61
|
+
/** True when the events are still queued and will be re-sent. */
|
|
62
|
+
readonly willRetry: boolean;
|
|
63
|
+
constructor(kind: CoreErrorKind, message: string, events: E[], options?: {
|
|
64
|
+
status?: number;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Every event the core sends carries the key the ingestor deduplicates on. */
|
|
68
|
+
export interface CoreEvent {
|
|
69
|
+
idempotencyKey: string;
|
|
70
|
+
}
|
|
71
|
+
/** The subset of a fetch Response the core reads. */
|
|
72
|
+
export interface CoreResponse {
|
|
73
|
+
status: number;
|
|
74
|
+
ok?: boolean;
|
|
75
|
+
headers: {
|
|
76
|
+
get(name: string): string | null;
|
|
77
|
+
};
|
|
78
|
+
text?(): Promise<string>;
|
|
79
|
+
json?(): Promise<unknown>;
|
|
80
|
+
}
|
|
81
|
+
/** The subset of fetch the core calls. Injectable for tests. */
|
|
82
|
+
export type CoreFetch = (url: string, init: {
|
|
83
|
+
method: string;
|
|
84
|
+
headers: Record<string, string>;
|
|
85
|
+
body: string;
|
|
86
|
+
signal: AbortSignal;
|
|
87
|
+
redirect: 'manual';
|
|
88
|
+
}) => Promise<CoreResponse>;
|
|
89
|
+
/** Sleep that ends early (resolving) when `signal` aborts. Injectable for tests. */
|
|
90
|
+
export type CoreSleep = (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
91
|
+
export interface CoreLogger {
|
|
92
|
+
debug(message: string): void;
|
|
93
|
+
warn(message: string): void;
|
|
94
|
+
error(message: string): void;
|
|
95
|
+
}
|
|
96
|
+
export interface TransportCoreOptions<E extends CoreEvent> {
|
|
97
|
+
/** Ingestor base URL. Must be https:// (plain http only for localhost, 127.0.0.1, ::1, or with allowInsecureHttp). */
|
|
98
|
+
baseUrl: string;
|
|
99
|
+
/** Tenant API key, sent only as X-API-Key. Never logged, never in an error or onDrop payload. */
|
|
100
|
+
apiKey: string;
|
|
101
|
+
/** Allow plain http:// to a non-local host. Default false. */
|
|
102
|
+
allowInsecureHttp?: boolean;
|
|
103
|
+
/** Path appended to baseUrl. Default '/v1/ingest/batch'. */
|
|
104
|
+
path?: string;
|
|
105
|
+
/** Sent as User-Agent, e.g. 'aforo-node/2.0.0'. */
|
|
106
|
+
userAgent?: string;
|
|
107
|
+
/** Extra request headers (e.g. X-Tenant-Id). Content-Type, X-API-Key and Content-Encoding cannot be overridden. */
|
|
108
|
+
headers?: Record<string, string>;
|
|
109
|
+
/** Events per request. Default 500, clamped to 1..1000. */
|
|
110
|
+
flushCount?: number;
|
|
111
|
+
/** Flush at least this often, in ms. Default 5000. */
|
|
112
|
+
flushIntervalMs?: number;
|
|
113
|
+
/** Queue capacity. Default 10 000. Overflow evicts the oldest event. */
|
|
114
|
+
maxQueueSize?: number;
|
|
115
|
+
/** Retries per batch before the breaker opens. Default 3. */
|
|
116
|
+
maxRetries?: number;
|
|
117
|
+
/** Backoff base in ms. Default 500. */
|
|
118
|
+
retryBaseMs?: number;
|
|
119
|
+
/** Backoff cap in ms. Default 30 000. */
|
|
120
|
+
retryMaxDelayMs?: number;
|
|
121
|
+
/** Retry-After cap in ms. Default 60 000. */
|
|
122
|
+
retryAfterMaxMs?: number;
|
|
123
|
+
/** First breaker cooldown in ms. Default 5000 (doubles, full jitter). */
|
|
124
|
+
breakerInitialMs?: number;
|
|
125
|
+
/** Breaker cooldown cap in ms. Default 300 000. */
|
|
126
|
+
breakerMaxMs?: number;
|
|
127
|
+
/** Total deadline per request in ms. Default 10 000. */
|
|
128
|
+
timeoutMs?: number;
|
|
129
|
+
/** shutdown() deadline in ms. Default 5000. */
|
|
130
|
+
shutdownTimeoutMs?: number;
|
|
131
|
+
/** Split a batch whose JSON body exceeds this many bytes. Default 4 MiB. */
|
|
132
|
+
targetRequestBodyBytes?: number;
|
|
133
|
+
/** Flush on the process 'beforeExit' event (within shutdownTimeoutMs). Default true. */
|
|
134
|
+
flushOnExit?: boolean;
|
|
135
|
+
/** Receives events that are lost for good. A throw is caught and logged. */
|
|
136
|
+
onDrop?: (events: E[], reason: CoreDropReason, detail?: string) => void;
|
|
137
|
+
/**
|
|
138
|
+
* Receives failures worth surfacing: breaker openings (`outage`, `auth`),
|
|
139
|
+
* rejections, shutdown leftovers and post-close tracking. Branch on
|
|
140
|
+
* `error.kind`, never on the message. A throw is caught and logged.
|
|
141
|
+
*/
|
|
142
|
+
onError?: (error: CoreTransportError<E>) => void;
|
|
143
|
+
/**
|
|
144
|
+
* Called after every accepted (2xx) batch with the response body, the
|
|
145
|
+
* `{success, data}` envelope already unwrapped (`undefined` when the body was
|
|
146
|
+
* empty or not JSON), and the events of that batch. Use it to read fields
|
|
147
|
+
* such as `killedSessionIds`. A throw is caught and logged.
|
|
148
|
+
*/
|
|
149
|
+
onAccepted?: (body: unknown, events: E[]) => void;
|
|
150
|
+
/** Log sink. Default: console with `logPrefix` (debug lines only when AFORO_DEBUG is set). */
|
|
151
|
+
logger?: CoreLogger;
|
|
152
|
+
/** Prefix for default log lines. Default '[aforo]'. */
|
|
153
|
+
logPrefix?: string;
|
|
154
|
+
/** Injectable for tests. Default: the global fetch, looked up on every request. */
|
|
155
|
+
fetch?: CoreFetch;
|
|
156
|
+
/** Injectable for tests. Default Date.now. */
|
|
157
|
+
clock?: () => number;
|
|
158
|
+
/** Injectable for tests. Default: an unref'd timer that ends when the signal aborts. */
|
|
159
|
+
sleep?: CoreSleep;
|
|
160
|
+
/** Injectable for tests. Default Math.random. */
|
|
161
|
+
random?: () => number;
|
|
162
|
+
}
|
|
163
|
+
/** What one flush cycle did. */
|
|
164
|
+
export interface CoreFlushResult {
|
|
165
|
+
/** Events the ingestor accepted (including duplicates it already had). */
|
|
166
|
+
sent: number;
|
|
167
|
+
/** Events dropped for good in this cycle. */
|
|
168
|
+
failed: number;
|
|
169
|
+
/** Events put back on the queue (outage or per-event retryable). */
|
|
170
|
+
requeued: number;
|
|
171
|
+
/** Events still queued when the cycle ended. */
|
|
172
|
+
queued: number;
|
|
173
|
+
}
|
|
174
|
+
/** What shutdown() did. */
|
|
175
|
+
export interface CoreShutdownResult extends CoreFlushResult {
|
|
176
|
+
/** Events still queued at the deadline, reported as 'shutdown'. */
|
|
177
|
+
leftover: number;
|
|
178
|
+
/** True when the deadline passed before the queue was empty. */
|
|
179
|
+
timedOut: boolean;
|
|
180
|
+
}
|
|
181
|
+
export declare const CORE_DEFAULTS: Readonly<{
|
|
182
|
+
path: "/v1/ingest/batch";
|
|
183
|
+
flushCount: 500;
|
|
184
|
+
maxBatchEvents: 1000;
|
|
185
|
+
flushIntervalMs: 5000;
|
|
186
|
+
maxQueueSize: 10000;
|
|
187
|
+
maxRetries: 3;
|
|
188
|
+
retryBaseMs: 500;
|
|
189
|
+
retryMaxDelayMs: 30000;
|
|
190
|
+
retryAfterMaxMs: 60000;
|
|
191
|
+
breakerInitialMs: 5000;
|
|
192
|
+
breakerMaxMs: 300000;
|
|
193
|
+
timeoutMs: 10000;
|
|
194
|
+
shutdownTimeoutMs: 5000;
|
|
195
|
+
targetRequestBodyBytes: number;
|
|
196
|
+
}>;
|
|
197
|
+
declare const INSPECT: unique symbol;
|
|
198
|
+
/**
|
|
199
|
+
* Throws when `baseUrl` is not https:// (plain http only for a local host or
|
|
200
|
+
* with `allowInsecureHttp`). Returns the URL without trailing slashes.
|
|
201
|
+
*/
|
|
202
|
+
export declare function checkBaseUrl(baseUrl: string, allowInsecureHttp?: boolean): string;
|
|
203
|
+
/** Full-jitter backoff: random(0, min(maxDelay, base * 2^attempt)). */
|
|
204
|
+
export declare function fullJitter(attempt: number, baseMs: number, maxMs: number, random: () => number): number;
|
|
205
|
+
/**
|
|
206
|
+
* Retry-After in ms: delta-seconds or an HTTP-date, capped at `maxMs`.
|
|
207
|
+
* Undefined when absent or unreadable (the caller then uses its own delay).
|
|
208
|
+
*/
|
|
209
|
+
export declare function parseRetryAfter(value: string | null | undefined, now: number, maxMs: number): number | undefined;
|
|
210
|
+
/** Sleep on an unref'd timer; resolves early when `signal` aborts. */
|
|
211
|
+
export declare const defaultSleep: CoreSleep;
|
|
212
|
+
/**
|
|
213
|
+
* The Node transport core. One instance per SDK client.
|
|
214
|
+
*
|
|
215
|
+
* ```ts
|
|
216
|
+
* const core = new TransportCore<MyEvent>({ baseUrl, apiKey, onDrop });
|
|
217
|
+
* core.enqueue(event); // never sends, never throws
|
|
218
|
+
* await core.flush(); // waits for the flusher to drain the queue
|
|
219
|
+
* await core.shutdown(); // drains until shutdownTimeoutMs, reports leftovers
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
export declare class TransportCore<E extends CoreEvent> {
|
|
223
|
+
private readonly url;
|
|
224
|
+
private readonly headers;
|
|
225
|
+
private readonly flushCount;
|
|
226
|
+
private readonly flushIntervalMs;
|
|
227
|
+
private readonly maxRetries;
|
|
228
|
+
private readonly retryBaseMs;
|
|
229
|
+
private readonly retryMaxDelayMs;
|
|
230
|
+
private readonly retryAfterMaxMs;
|
|
231
|
+
private readonly breakerInitialMs;
|
|
232
|
+
private readonly breakerMaxMs;
|
|
233
|
+
private readonly timeoutMs;
|
|
234
|
+
private readonly shutdownTimeoutMs;
|
|
235
|
+
private readonly targetBytes;
|
|
236
|
+
private readonly fetchFn;
|
|
237
|
+
private readonly clock;
|
|
238
|
+
private readonly sleepFn;
|
|
239
|
+
private readonly random;
|
|
240
|
+
private readonly logger;
|
|
241
|
+
private readonly onDrop?;
|
|
242
|
+
private readonly onError?;
|
|
243
|
+
private readonly onAccepted?;
|
|
244
|
+
private readonly queue;
|
|
245
|
+
private dropped;
|
|
246
|
+
private overflowDrops;
|
|
247
|
+
private invalidDrops;
|
|
248
|
+
private closedDrops;
|
|
249
|
+
private sentTotal;
|
|
250
|
+
private inFlight;
|
|
251
|
+
private closedFlag;
|
|
252
|
+
private deadline;
|
|
253
|
+
private readonly stopController;
|
|
254
|
+
/** Aborted to wake the flusher from its idle wait (replaced after each wake). */
|
|
255
|
+
private idleWake;
|
|
256
|
+
/** Aborted to cut a breaker wait short (explicit flush / shutdown). */
|
|
257
|
+
private breakerWake;
|
|
258
|
+
private breakerOpens;
|
|
259
|
+
private breakerOpen;
|
|
260
|
+
/** ERROR line for this breaker opening (401/403 or a redirect) already written. */
|
|
261
|
+
private configErrorLogged;
|
|
262
|
+
private flushWaiters;
|
|
263
|
+
private readonly loop;
|
|
264
|
+
private shutdownPromise;
|
|
265
|
+
private readonly closingTotals;
|
|
266
|
+
private deadlineTimer;
|
|
267
|
+
/** Per-event retryable failures seen so far, keyed by event object. */
|
|
268
|
+
private readonly perEventRetries;
|
|
269
|
+
/** Highest per-event retry count in the last requeue, for the backoff before the resend. */
|
|
270
|
+
private perEventAttempt;
|
|
271
|
+
/** Each queued event's JSON, taken at enqueue. */
|
|
272
|
+
private readonly serialized;
|
|
273
|
+
/** Events taken off the queue that are being sent or waiting for a retry. */
|
|
274
|
+
private eventsInHand;
|
|
275
|
+
private shutdownSettled;
|
|
276
|
+
private exitKeepAlive;
|
|
277
|
+
constructor(options: TransportCoreOptions<E>);
|
|
278
|
+
/**
|
|
279
|
+
* Queue an event. Never sends and never throws. The event is serialized
|
|
280
|
+
* now, so later changes to the object are not sent; one JSON cannot
|
|
281
|
+
* represent is reported through onDrop(…, 'invalid'). After shutdown the
|
|
282
|
+
* event is reported through onDrop(…, 'closed'). Returns false in both cases.
|
|
283
|
+
*/
|
|
284
|
+
enqueue(event: E): boolean;
|
|
285
|
+
/** Count and report events the caller rejected before queueing (reason 'invalid' or another). */
|
|
286
|
+
reportDrop(events: E[], reason: CoreDropReason, detail?: string): void;
|
|
287
|
+
/**
|
|
288
|
+
* Hand the queue to the flusher and wait for one drain cycle. Never runs a
|
|
289
|
+
* second concurrent send. While the breaker is open this makes one attempt
|
|
290
|
+
* now instead of waiting for the cooldown.
|
|
291
|
+
*/
|
|
292
|
+
flush(): Promise<CoreFlushResult>;
|
|
293
|
+
/**
|
|
294
|
+
* Stop accepting events and drain until `shutdownTimeoutMs`. Each request
|
|
295
|
+
* in that window uses min(timeoutMs, time left). Events still queued at the
|
|
296
|
+
* deadline are reported through onDrop(…, 'shutdown'). Idempotent.
|
|
297
|
+
*/
|
|
298
|
+
shutdown(): Promise<CoreShutdownResult>;
|
|
299
|
+
/** Events waiting in the queue. */
|
|
300
|
+
get queued(): number;
|
|
301
|
+
/** Events lost for good since creation (every reason, including unattributed per-event rejections). */
|
|
302
|
+
get droppedCount(): number;
|
|
303
|
+
/** Events the ingestor accepted since creation. */
|
|
304
|
+
get sentCount(): number;
|
|
305
|
+
/** True after shutdown() was called. */
|
|
306
|
+
get closed(): boolean;
|
|
307
|
+
/** True while the breaker pauses the flusher after an outage. */
|
|
308
|
+
get isBreakerOpen(): boolean;
|
|
309
|
+
/** Ingest requests in flight right now (0 or 1). */
|
|
310
|
+
get inFlightRequests(): number;
|
|
311
|
+
/** True while events are queued or a taken batch is being sent / retried. */
|
|
312
|
+
get busy(): boolean;
|
|
313
|
+
/**
|
|
314
|
+
* @internal Called from the process 'beforeExit' hook. When this core still
|
|
315
|
+
* holds events (or a shutdown is already running), it shuts down and holds
|
|
316
|
+
* the event loop open with one timer until the shutdown settles. Never
|
|
317
|
+
* longer than the shutdown deadline plus a second.
|
|
318
|
+
*/
|
|
319
|
+
flushForExit(): void;
|
|
320
|
+
/** @internal Debug log, used by the exit hook. */
|
|
321
|
+
logDebug(message: string): void;
|
|
322
|
+
toJSON(): Record<string, unknown>;
|
|
323
|
+
[INSPECT](): string;
|
|
324
|
+
private wakeIdle;
|
|
325
|
+
private wakeBreaker;
|
|
326
|
+
private get pastDeadline();
|
|
327
|
+
private run;
|
|
328
|
+
private waitBackoff;
|
|
329
|
+
private waitIdle;
|
|
330
|
+
private waitBreaker;
|
|
331
|
+
/** Send everything queued, one request at a time. Stops on outage or deadline. */
|
|
332
|
+
private drainCycle;
|
|
333
|
+
/**
|
|
334
|
+
* Send one batch (splitting it when the body is too large or the ingestor
|
|
335
|
+
* answers 413). Returns true when the cycle must stop (outage or deadline);
|
|
336
|
+
* unsent events are already back at the front of the queue then.
|
|
337
|
+
*/
|
|
338
|
+
private sendBatch;
|
|
339
|
+
/** Request body from the JSON taken at enqueue. */
|
|
340
|
+
private bodyFor;
|
|
341
|
+
private requeue;
|
|
342
|
+
/** `kept` are the events just put back on the queue (named in the onError report). */
|
|
343
|
+
private openBreaker;
|
|
344
|
+
private closeBreaker;
|
|
345
|
+
/** One chunk, with retries. Never throws. */
|
|
346
|
+
private sendWithRetry;
|
|
347
|
+
private post;
|
|
348
|
+
private handleAccepted;
|
|
349
|
+
private discardBody;
|
|
350
|
+
private readProblem;
|
|
351
|
+
private recordDrop;
|
|
352
|
+
private reportError;
|
|
353
|
+
}
|
|
354
|
+
export {};
|
|
355
|
+
//# sourceMappingURL=core.d.ts.map
|