@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.
Files changed (91) hide show
  1. package/README.md +71 -25
  2. package/dist/cjs/client.d.ts +33 -35
  3. package/dist/cjs/client.d.ts.map +1 -1
  4. package/dist/cjs/client.js +193 -147
  5. package/dist/cjs/client.js.map +1 -1
  6. package/dist/cjs/idempotency.d.ts +9 -9
  7. package/dist/cjs/idempotency.js +9 -9
  8. package/dist/cjs/index.d.ts +8 -8
  9. package/dist/cjs/index.d.ts.map +1 -1
  10. package/dist/cjs/index.js +20 -19
  11. package/dist/cjs/index.js.map +1 -1
  12. package/dist/cjs/limits.d.ts +47 -7
  13. package/dist/cjs/limits.d.ts.map +1 -1
  14. package/dist/cjs/limits.js +65 -10
  15. package/dist/cjs/limits.js.map +1 -1
  16. package/dist/cjs/middleware/common.d.ts +24 -2
  17. package/dist/cjs/middleware/common.d.ts.map +1 -1
  18. package/dist/cjs/middleware/common.js +66 -4
  19. package/dist/cjs/middleware/common.js.map +1 -1
  20. package/dist/cjs/middleware/express.d.ts +11 -6
  21. package/dist/cjs/middleware/express.d.ts.map +1 -1
  22. package/dist/cjs/middleware/express.js +25 -62
  23. package/dist/cjs/middleware/express.js.map +1 -1
  24. package/dist/cjs/middleware/fastify.d.ts +9 -5
  25. package/dist/cjs/middleware/fastify.d.ts.map +1 -1
  26. package/dist/cjs/middleware/fastify.js +28 -48
  27. package/dist/cjs/middleware/fastify.js.map +1 -1
  28. package/dist/cjs/middleware/koa.d.ts +11 -6
  29. package/dist/cjs/middleware/koa.d.ts.map +1 -1
  30. package/dist/cjs/middleware/koa.js +28 -46
  31. package/dist/cjs/middleware/koa.js.map +1 -1
  32. package/dist/cjs/transport-core/core.d.ts +355 -0
  33. package/dist/cjs/transport-core/core.d.ts.map +1 -0
  34. package/dist/cjs/transport-core/core.js +1047 -0
  35. package/dist/cjs/transport-core/core.js.map +1 -0
  36. package/dist/cjs/types.d.ts +108 -20
  37. package/dist/cjs/types.d.ts.map +1 -1
  38. package/dist/esm/client.js +191 -145
  39. package/dist/esm/client.js.map +1 -1
  40. package/dist/esm/idempotency.js +9 -9
  41. package/dist/esm/index.js +7 -7
  42. package/dist/esm/index.js.map +1 -1
  43. package/dist/esm/limits.js +62 -10
  44. package/dist/esm/limits.js.map +1 -1
  45. package/dist/esm/middleware/common.js +62 -4
  46. package/dist/esm/middleware/common.js.map +1 -1
  47. package/dist/esm/middleware/express.js +24 -61
  48. package/dist/esm/middleware/express.js.map +1 -1
  49. package/dist/esm/middleware/fastify.js +27 -47
  50. package/dist/esm/middleware/fastify.js.map +1 -1
  51. package/dist/esm/middleware/koa.js +27 -45
  52. package/dist/esm/middleware/koa.js.map +1 -1
  53. package/dist/esm/package.json +1 -0
  54. package/dist/esm/transport-core/core.js +1038 -0
  55. package/dist/esm/transport-core/core.js.map +1 -0
  56. package/dist/types/client.d.ts +33 -35
  57. package/dist/types/client.d.ts.map +1 -1
  58. package/dist/types/idempotency.d.ts +9 -9
  59. package/dist/types/index.d.ts +8 -8
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/dist/types/limits.d.ts +47 -7
  62. package/dist/types/limits.d.ts.map +1 -1
  63. package/dist/types/middleware/common.d.ts +24 -2
  64. package/dist/types/middleware/common.d.ts.map +1 -1
  65. package/dist/types/middleware/express.d.ts +11 -6
  66. package/dist/types/middleware/express.d.ts.map +1 -1
  67. package/dist/types/middleware/fastify.d.ts +9 -5
  68. package/dist/types/middleware/fastify.d.ts.map +1 -1
  69. package/dist/types/middleware/koa.d.ts +11 -6
  70. package/dist/types/middleware/koa.d.ts.map +1 -1
  71. package/dist/types/transport-core/core.d.ts +355 -0
  72. package/dist/types/transport-core/core.d.ts.map +1 -0
  73. package/dist/types/types.d.ts +108 -20
  74. package/dist/types/types.d.ts.map +1 -1
  75. package/package.json +22 -6
  76. package/dist/cjs/buffer.d.ts +0 -27
  77. package/dist/cjs/buffer.d.ts.map +0 -1
  78. package/dist/cjs/buffer.js +0 -75
  79. package/dist/cjs/buffer.js.map +0 -1
  80. package/dist/cjs/transport.d.ts +0 -39
  81. package/dist/cjs/transport.d.ts.map +0 -1
  82. package/dist/cjs/transport.js +0 -116
  83. package/dist/cjs/transport.js.map +0 -1
  84. package/dist/esm/buffer.js +0 -71
  85. package/dist/esm/buffer.js.map +0 -1
  86. package/dist/esm/transport.js +0 -112
  87. package/dist/esm/transport.js.map +0 -1
  88. package/dist/types/buffer.d.ts +0 -27
  89. package/dist/types/buffer.d.ts.map +0 -1
  90. package/dist/types/transport.d.ts +0 -39
  91. package/dist/types/transport.d.ts.map +0 -1
@@ -1,79 +1,92 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AforoClient = void 0;
4
- const buffer_1 = require("./buffer");
5
- const transport_1 = require("./transport");
6
- const idempotency_1 = require("./idempotency");
7
- const limits_1 = require("./limits");
3
+ exports.AforoClient = exports.SDK_VERSION = void 0;
4
+ const core_js_1 = require("./transport-core/core.js");
5
+ const idempotency_js_1 = require("./idempotency.js");
6
+ const limits_js_1 = require("./limits.js");
7
+ /** Package version, sent in User-Agent. Keep in step with package.json. */
8
+ exports.SDK_VERSION = '2.0.0';
8
9
  const DEFAULT_BASE_URL = 'https://api.aforo.ai';
9
10
  const DEFAULT_PRODUCT_TYPE = 'API';
10
11
  const DEFAULT_SESSION_PRODUCT_TYPE = 'AI_AGENT';
11
- /** Ingestor hard limit on events per batch request (IngestBatchRequest @Size(max = 1000)). */
12
- const MAX_BATCH_SIZE = 1000;
13
12
  const HEARTBEAT_INTERVAL_MS = 30000;
14
13
  const HEARTBEAT_METRIC = 'system.session.heartbeat';
15
14
  /** Heartbeats carry no billable customer; the ingestor intercepts them before billing. */
16
15
  const HEARTBEAT_CUSTOMER_ID = 'system';
17
- const DEFAULT_FLUSH_COUNT = 50;
18
- const DEFAULT_FLUSH_INTERVAL = 5000;
19
- const DEFAULT_MAX_QUEUE_SIZE = 10000;
20
- const DEFAULT_MAX_RETRIES = 3;
21
- const DEFAULT_RETRY_BASE_MS = 1000;
22
16
  const DEFAULT_TIMEOUT = 10000;
23
- const DEFAULT_SHUTDOWN_TIMEOUT = 5000;
17
+ function makeHeartbeatSender(baseUrl, apiKey, timeoutMs) {
18
+ const url = `${baseUrl}/v1/ingest/batch`;
19
+ return async (event) => {
20
+ try {
21
+ const response = await globalThis.fetch(url, {
22
+ method: 'POST',
23
+ headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey, 'User-Agent': `aforo-node/${exports.SDK_VERSION}` },
24
+ body: JSON.stringify({ events: [event] }),
25
+ signal: AbortSignal.timeout(timeoutMs),
26
+ // Never follow a redirect: it would send the API key to another host.
27
+ redirect: 'manual',
28
+ });
29
+ const status = Number(response?.status);
30
+ if (status === 0 || (status >= 300 && status < 400)) {
31
+ console.debug(`[aforo] heartbeat not delivered: the ingestor answered HTTP ${status} (redirects are not followed; check baseUrl)`);
32
+ return false;
33
+ }
34
+ return Boolean(response?.ok);
35
+ }
36
+ catch (err) {
37
+ console.debug(`[aforo] heartbeat not delivered: ${err?.message ?? err}`);
38
+ return false;
39
+ }
40
+ };
41
+ }
24
42
  /**
25
43
  * Aforo metering client.
26
44
  *
27
- * Enqueues usage events into a ring buffer, flushes them in batches
28
- * to the Aforo ingestor service via HTTP. Non-blocking — `track()`
29
- * returns immediately, flushing happens in the background.
45
+ * `track()` validates the event and puts it on a bounded queue; one background
46
+ * flusher sends batches to the ingestor. If the ingestor is down, batches go
47
+ * back on the queue (same idempotency keys) and are sent when it recovers.
30
48
  *
31
49
  * ```typescript
32
- * const client = new AforoClient({ apiKey: 'your-key', productType: 'API' });
50
+ * const client = new AforoClient({ apiKey: process.env.AFORO_API_KEY!, productType: 'API' });
33
51
  * await client.track({ customerId: 'cust_1', metricName: 'api_calls', quantity: 1 });
34
- * // On shutdown:
35
- * await client.shutdown();
52
+ * // The SDK installs no signal handlers. Flush from your own:
53
+ * process.once('SIGTERM', async () => { await client.shutdown(); process.exit(0); });
36
54
  * ```
37
55
  */
38
56
  class AforoClient {
39
57
  constructor(options) {
40
- this.flushTimer = null;
41
- this.flushing = false;
42
- this.closed = false;
43
- this.pendingFlush = null;
44
58
  // Session heartbeat state
45
59
  this.heartbeatTimer = null;
46
60
  this.activeSessionId = null;
47
61
  this.sessionStartedAt = null;
48
62
  this.sessionProductType = DEFAULT_SESSION_PRODUCT_TYPE;
49
- if (!options.apiKey)
63
+ if (!options || !options.apiKey)
50
64
  throw new Error('apiKey is required');
51
- this.flushCount = Math.max(1, Math.min(options.flushCount ?? DEFAULT_FLUSH_COUNT, MAX_BATCH_SIZE));
65
+ const baseUrl = (0, core_js_1.checkBaseUrl)(options.baseUrl ?? DEFAULT_BASE_URL, options.allowInsecureHttp === true);
66
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
52
67
  this.productType = normalizeProductType(options.productType) ?? DEFAULT_PRODUCT_TYPE;
53
- this.flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL;
54
- this.shutdownTimeoutMs = options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT;
55
- this.buffer = new buffer_1.RingBuffer(options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE);
56
- this.transport = new transport_1.Transport({
57
- baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
68
+ const onDrop = options.onDrop;
69
+ this.core = new core_js_1.TransportCore({
70
+ baseUrl,
58
71
  apiKey: options.apiKey,
59
- timeout: options.timeout ?? DEFAULT_TIMEOUT,
60
- maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
61
- retryBaseMs: options.retryBaseMs ?? DEFAULT_RETRY_BASE_MS,
72
+ allowInsecureHttp: options.allowInsecureHttp,
73
+ userAgent: `aforo-node/${exports.SDK_VERSION}`,
74
+ flushCount: options.flushCount,
75
+ flushIntervalMs: options.flushInterval,
76
+ maxQueueSize: options.maxQueueSize,
77
+ maxRetries: options.maxRetries,
78
+ retryBaseMs: options.retryBaseMs,
79
+ timeoutMs: timeout,
80
+ shutdownTimeoutMs: options.shutdownTimeoutMs,
81
+ flushOnExit: options.flushOnExit,
82
+ logPrefix: '[aforo]',
83
+ onDrop: onDrop ? (events, reason) => onDrop(events, reason) : undefined,
84
+ });
85
+ // Non-enumerable, so inspect()/JSON never walk into the closure's owner.
86
+ Object.defineProperty(this, 'sendHeartbeat', {
87
+ value: makeHeartbeatSender(baseUrl, options.apiKey, timeout),
88
+ enumerable: false,
62
89
  });
63
- // Start periodic flush timer
64
- this.flushTimer = setInterval(() => {
65
- this.flush().catch(() => { });
66
- }, this.flushInterval);
67
- // Unref the timer so it doesn't prevent Node from exiting
68
- if (this.flushTimer && typeof this.flushTimer === 'object' && 'unref' in this.flushTimer) {
69
- this.flushTimer.unref();
70
- }
71
- // Register graceful shutdown handlers
72
- const shutdownHandler = () => {
73
- this.shutdown().catch(() => { });
74
- };
75
- process.once('SIGTERM', shutdownHandler);
76
- process.once('SIGINT', shutdownHandler);
77
90
  }
78
91
  // ─── Session lifecycle with heartbeat ─────────────────────────────
79
92
  /**
@@ -90,7 +103,7 @@ class AforoClient {
90
103
  * @param productType - Product type for the session (default AI_AGENT)
91
104
  */
92
105
  startSession(sessionId, productType = DEFAULT_SESSION_PRODUCT_TYPE) {
93
- if (this.closed || !sessionId || !String(sessionId).trim())
106
+ if (this.core.closed || !sessionId || !String(sessionId).trim())
94
107
  return;
95
108
  this.stopHeartbeatTimer();
96
109
  this.activeSessionId = String(sessionId);
@@ -121,7 +134,7 @@ class AforoClient {
121
134
  }
122
135
  emitSessionHeartbeat(boundary) {
123
136
  const sessionId = this.activeSessionId;
124
- if (!sessionId || (this.closed && boundary === 'HEARTBEAT'))
137
+ if (!sessionId || (this.core.closed && boundary === 'HEARTBEAT'))
125
138
  return Promise.resolve();
126
139
  const now = Date.now();
127
140
  const occurredAt = new Date(now).toISOString();
@@ -145,62 +158,35 @@ class AforoClient {
145
158
  sdkLanguage: 'node',
146
159
  },
147
160
  };
148
- return this.transport.sendSingleBestEffort(heartbeat).then(() => undefined, () => undefined);
161
+ // Sent alone and outside the usage queue; a failed heartbeat is not a
162
+ // usage drop, so it is never counted in droppedCount or handed to onDrop.
163
+ return this.sendHeartbeat(heartbeat).then(() => undefined, () => undefined);
149
164
  }
150
165
  // ─── Event tracking ──────────────────────────────────────────────
151
166
  /**
152
- * Enqueue a usage event for batched delivery.
153
- * Returns immediately — does not await HTTP.
154
- * Triggers a flush if the buffer reaches flushCount.
167
+ * Queue a usage event. Returns at once — it never waits on HTTP and never
168
+ * starts a request (the background flusher sends batches).
155
169
  *
156
- * Throws if the event breaks any constraint the ingestor enforces — a blank
157
- * or over-long `customerId` / `metricName` / `idempotencyKey`, a `quantity`
158
- * that is not positive or carries more than 14 integer digits / 6 decimal
159
- * places, an `occurredAt` that is not an ISO-8601 instant, or any other
160
- * capped field that is too long. Limits the server makes configurable (event
161
- * age, clock skew, metadata size) are left to the server, so the SDK can
162
- * never refuse usage a given deployment would accept. Such an event is rejected by the ingestor and never billed,
163
- * and because flushing happens in the background nobody would see that
164
- * rejection — so it is reported here instead, and never enters the buffer.
165
- * See `limits.ts` for the limits and where each one comes from.
170
+ * Never throws. An event the ingestor would reject — a blank or over-long
171
+ * `customerId` / `metricName` / `idempotencyKey`, a `quantity` that is not
172
+ * positive or carries more than 14 integer digits / 6 decimal places, an
173
+ * `occurredAt` that is not an ISO-8601 instant, or any other capped field
174
+ * that is too long — is NOT queued and NOT sent. It is counted in
175
+ * `droppedCount`, WARN-logged with the field and the limit, and passed to
176
+ * the opt-in `onDrop` hook with reason `'invalid'`. After `shutdown()` the
177
+ * event is counted and reported with reason `'closed'`.
166
178
  */
167
179
  async track(event) {
168
- if (this.closed) {
169
- throw new Error('AforoClient is shut down — cannot track new events');
170
- }
171
- if (event.customerId === undefined || event.customerId === null || !String(event.customerId).trim()) {
172
- throw new Error('customerId is required');
173
- }
174
- if (event.metricName === undefined || event.metricName === null || !String(event.metricName).trim()) {
175
- throw new Error('metricName is required');
176
- }
180
+ // A missing event object is reported as an invalid event, not a TypeError.
181
+ event = (event ?? {});
177
182
  const occurredAt = resolveOccurredAt(event.occurredAt);
178
183
  const quantity = event.quantity ?? 1;
179
- if (typeof quantity !== 'number' || !Number.isFinite(quantity) || quantity <= 0) {
180
- throw new Error('quantity must be a positive number (> 0)');
181
- }
182
184
  const productType = normalizeProductType(event.productType) ?? this.productType;
183
185
  // Minted once, here, when the event is enqueued — never at flush/retry
184
- // time, so a retried batch carries the same keys and the ingestor
185
- // deduplicates it. A caller-supplied key is passed through verbatim;
186
- // otherwise each event gets its own random UUID. A deterministic hash of
187
- // the event fields would make two genuinely distinct events in the same
188
- // millisecond collide, and the ingestor would silently drop the second.
189
- // Minted before the limit check so an over-long caller-supplied key is
190
- // reported here rather than silently dropped by the server later.
191
- const idempotencyKey = event.idempotencyKey ?? (0, idempotency_1.generateRandomKey)();
192
- const violation = (0, limits_1.describeLimitViolation)({
193
- customerId: event.customerId,
194
- metricName: event.metricName,
195
- quantity,
196
- idempotencyKey,
197
- occurredAt,
198
- productType,
199
- endpointPath: event.endpointPath,
200
- httpMethod: event.httpMethod,
201
- });
202
- if (violation)
203
- throw new Error(violation);
186
+ // time, so a retried or requeued batch carries the same keys and the
187
+ // ingestor deduplicates it. A caller-supplied key is passed through
188
+ // verbatim; otherwise each event gets its own random UUID.
189
+ const idempotencyKey = event.idempotencyKey ?? (0, idempotency_js_1.generateRandomKey)();
204
190
  const resolved = {
205
191
  customerId: event.customerId,
206
192
  metricName: event.metricName,
@@ -214,71 +200,68 @@ class AforoClient {
214
200
  ...(event.statusCode !== undefined ? { statusCode: event.statusCode } : {}),
215
201
  ...(event.responseTimeMs !== undefined ? { responseTimeMs: event.responseTimeMs } : {}),
216
202
  };
217
- this.buffer.push(resolved);
218
- // Trigger flush if buffer threshold reached
219
- if (this.buffer.size >= this.flushCount) {
220
- this.flush().catch(() => { });
203
+ const executionStatus = normalizeExecutionStatus(event.executionStatus);
204
+ if (executionStatus) {
205
+ if (CANONICAL_EXECUTION_STATUSES.has(executionStatus)) {
206
+ resolved.executionStatus = executionStatus;
207
+ }
208
+ else {
209
+ // The ingestor rejects an event carrying an unknown status, so an
210
+ // unknown value is reported and the field omitted (the event still
211
+ // bills, at full weight) instead of losing the event.
212
+ warnUnknownExecutionStatus(executionStatus);
213
+ }
214
+ }
215
+ if (this.core.closed) {
216
+ this.core.enqueue(resolved); // reported as 'closed'
217
+ return;
218
+ }
219
+ const violation = describeInvalidEvent(event, resolved);
220
+ if (violation) {
221
+ this.core.reportDrop([resolved], 'invalid', violation);
222
+ return;
221
223
  }
224
+ this.core.enqueue(resolved);
225
+ }
226
+ /** @internal Count and report an event a middleware could not build (reason 'invalid'). */
227
+ reportInvalid(event, detail) {
228
+ this.core.reportDrop([event], 'invalid', detail);
222
229
  }
223
230
  /**
224
- * Force-flush all buffered events to the ingestor.
225
- * Safe to call concurrently — only one flush runs at a time.
231
+ * Send everything queued now and wait for it. Runs on the single flusher —
232
+ * never a second concurrent request. While the ingestor is down this makes
233
+ * one attempt; unsent events stay queued.
226
234
  */
227
235
  async flush() {
228
- if (this.flushing && this.pendingFlush) {
229
- return this.pendingFlush;
230
- }
231
- this.flushing = true;
232
- this.pendingFlush = this.doFlush();
233
- try {
234
- return await this.pendingFlush;
235
- }
236
- finally {
237
- this.flushing = false;
238
- this.pendingFlush = null;
239
- }
240
- }
241
- async doFlush() {
242
- let totalSent = 0;
243
- let totalFailed = 0;
244
- while (!this.buffer.isEmpty) {
245
- const batch = this.buffer.drainUpTo(this.flushCount);
246
- if (batch.length === 0)
247
- break;
248
- const result = await this.transport.send(batch);
249
- totalSent += result.sent;
250
- totalFailed += result.failed;
251
- }
252
- return { sent: totalSent, failed: totalFailed };
236
+ const r = await this.core.flush();
237
+ return { sent: r.sent, failed: r.failed, requeued: r.requeued, queued: r.queued };
253
238
  }
254
239
  /**
255
- * Flush remaining events and stop the client.
256
- * After shutdown, track() will throw.
240
+ * Stop accepting events and send what is queued, for at most
241
+ * `shutdownTimeoutMs`. Events still queued at the deadline are counted and
242
+ * reported through onDrop with reason 'shutdown'. Safe to call more than once.
257
243
  */
258
244
  async shutdown() {
259
- if (this.closed)
260
- return;
261
- this.closed = true;
262
- // Stop session heartbeats
263
245
  this.stopHeartbeatTimer();
264
- // Clear periodic flush timer
265
- if (this.flushTimer) {
266
- clearInterval(this.flushTimer);
267
- this.flushTimer = null;
268
- }
269
- // Flush with timeout
270
- await Promise.race([
271
- this.flush(),
272
- new Promise((resolve) => setTimeout(resolve, this.shutdownTimeoutMs)),
273
- ]);
246
+ await this.core.shutdown();
274
247
  }
275
- /** Number of events currently buffered. */
248
+ /** Number of events currently queued. */
276
249
  get bufferedCount() {
277
- return this.buffer.size;
250
+ return this.core.queued;
251
+ }
252
+ /** Total events permanently dropped (every DropReason) since creation. */
253
+ get droppedCount() {
254
+ return this.core.droppedCount;
278
255
  }
279
256
  /** Whether the client has been shut down. */
280
257
  get isShutdown() {
281
- return this.closed;
258
+ return this.core.closed;
259
+ }
260
+ toJSON() {
261
+ return { productType: this.productType, buffered: this.bufferedCount, dropped: this.droppedCount, shutdown: this.isShutdown };
262
+ }
263
+ [Symbol.for('nodejs.util.inspect.custom')]() {
264
+ return `AforoClient { productType: '${this.productType}', buffered: ${this.bufferedCount}, dropped: ${this.droppedCount}, shutdown: ${this.isShutdown} }`;
282
265
  }
283
266
  }
284
267
  exports.AforoClient = AforoClient;
@@ -288,14 +271,77 @@ function normalizeProductType(value) {
288
271
  const trimmed = String(value).trim();
289
272
  return trimmed ? trimmed.toUpperCase() : undefined;
290
273
  }
274
+ /** Shorten an offending value for a log line. */
275
+ function clip(value) {
276
+ return String(value).slice(0, 80);
277
+ }
278
+ /**
279
+ * First reason the ingestor would reject this event, or null when it is
280
+ * acceptable. Required fields and quantity sign first, then the size limits.
281
+ */
282
+ function describeInvalidEvent(event, resolved) {
283
+ for (const field of ['customerId', 'metricName']) {
284
+ const value = event?.[field];
285
+ if (value === undefined || value === null || !String(value).trim()) {
286
+ return `${field} is required (got "${clip(value)}")`;
287
+ }
288
+ }
289
+ const quantity = resolved.quantity;
290
+ if (typeof quantity !== 'number' || !Number.isFinite(quantity) || quantity <= 0) {
291
+ return `quantity must be a positive number (> 0), got "${clip(quantity)}"`;
292
+ }
293
+ if (typeof resolved.occurredAt !== 'string') {
294
+ return `occurredAt must be an ISO-8601 string or epoch milliseconds, got "${clip(resolved.occurredAt)}"`;
295
+ }
296
+ return (0, limits_js_1.describeLimitViolation)({
297
+ customerId: resolved.customerId,
298
+ metricName: resolved.metricName,
299
+ quantity,
300
+ idempotencyKey: resolved.idempotencyKey,
301
+ occurredAt: resolved.occurredAt,
302
+ productType: resolved.productType,
303
+ endpointPath: resolved.endpointPath,
304
+ httpMethod: resolved.httpMethod,
305
+ });
306
+ }
291
307
  function randomSuffix() {
292
308
  return Math.random().toString(36).slice(2, 10);
293
309
  }
294
310
  function resolveOccurredAt(value) {
295
311
  if (!value)
296
312
  return new Date().toISOString();
297
- if (typeof value === 'number')
298
- return new Date(value).toISOString();
313
+ if (typeof value === 'number') {
314
+ const date = new Date(value);
315
+ // An out-of-range epoch must not throw from track(); it is reported as invalid.
316
+ return Number.isNaN(date.getTime()) ? String(value) : date.toISOString();
317
+ }
299
318
  return value;
300
319
  }
320
+ /**
321
+ * Statuses the ingestor accepts (contract/ingest-contract.json, max 20 chars).
322
+ * Anything else would make it reject the event.
323
+ */
324
+ const CANONICAL_EXECUTION_STATUSES = new Set([
325
+ 'SUCCESS', 'PARTIAL', 'TIMEOUT', 'ERROR', 'VALIDATION_FAILED', 'FAILED',
326
+ 'FAILURE', 'CANCELLED', 'PENDING', 'BLOCKED', 'HITL_REQUIRED',
327
+ ]);
328
+ // Warn once per distinct bad value (bounded) so a misconfigured static status
329
+ // can't storm the log on every request.
330
+ const MAX_WARNED_STATUSES = 100;
331
+ const warnedStatuses = new Set();
332
+ function warnUnknownExecutionStatus(value) {
333
+ if (warnedStatuses.has(value))
334
+ return;
335
+ if (warnedStatuses.size < MAX_WARNED_STATUSES)
336
+ warnedStatuses.add(value);
337
+ console.warn(`[aforo] Unknown executionStatus "${value.slice(0, 40)}" — field omitted from the event. ` +
338
+ `Expected one of: ${[...CANONICAL_EXECUTION_STATUSES].join(', ')}.`);
339
+ }
340
+ /** Trim + upper-case; blank/absent → undefined (key omitted from the wire body). */
341
+ function normalizeExecutionStatus(value) {
342
+ if (typeof value !== 'string')
343
+ return undefined;
344
+ const trimmed = value.trim();
345
+ return trimmed ? trimmed.toUpperCase() : undefined;
346
+ }
301
347
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
@@ -4,18 +4,18 @@
4
4
  *
5
5
  * Formula: SHA256(customerId + metricName + quantity + occurredAt)
6
6
  *
7
- * NOT the client default any more. `occurredAt` only carries millisecond
8
- * precision, so two genuinely distinct events for the same customer + metric
9
- * + quantity inside one millisecond hash to the same key and the ingestor
10
- * drops the second as a DUPLICATE — silent under-billing. Kept exported for
11
- * callers who deliberately want content-addressed dedup (e.g. replaying a
12
- * fixed batch) and pass the result to `track({ idempotencyKey })` themselves.
7
+ * WARNING - collapse hazard: two legitimately DISTINCT events with identical
8
+ * fields in the same timestamp instant produce the SAME key, so the second
9
+ * dedups away (silent under-billing). The SDK therefore no longer uses this
10
+ * as the automatic fallback for keyless track() calls (2026-07-05 - mirrors
11
+ * Aforo ingest's April 2026 H4 fix). Use it only when your events are
12
+ * guaranteed unique per (customer, metric, quantity, occurredAt).
13
13
  */
14
14
  export declare function generateIdempotencyKey(customerId: string, metricName: string, quantity: number, occurredAt: string): string;
15
15
  /**
16
- * Generate a random UUID v4. This is the default key for an event whose caller
17
- * supplied none: every event gets its own key, so no two distinct events can
18
- * collide. Dedup stays opt-in via an explicit `idempotencyKey`.
16
+ * Generate a random UUID key - the automatic fallback for keyless track()
17
+ * calls. No caller key = dedup opt-out: every call is a distinct event; the
18
+ * key is stamped once at enqueue so flush retries stay dedup-safe.
19
19
  */
20
20
  export declare function generateRandomKey(): string;
21
21
  //# sourceMappingURL=idempotency.d.ts.map
@@ -9,21 +9,21 @@ const node_crypto_1 = require("node:crypto");
9
9
  *
10
10
  * Formula: SHA256(customerId + metricName + quantity + occurredAt)
11
11
  *
12
- * NOT the client default any more. `occurredAt` only carries millisecond
13
- * precision, so two genuinely distinct events for the same customer + metric
14
- * + quantity inside one millisecond hash to the same key and the ingestor
15
- * drops the second as a DUPLICATE — silent under-billing. Kept exported for
16
- * callers who deliberately want content-addressed dedup (e.g. replaying a
17
- * fixed batch) and pass the result to `track({ idempotencyKey })` themselves.
12
+ * WARNING - collapse hazard: two legitimately DISTINCT events with identical
13
+ * fields in the same timestamp instant produce the SAME key, so the second
14
+ * dedups away (silent under-billing). The SDK therefore no longer uses this
15
+ * as the automatic fallback for keyless track() calls (2026-07-05 - mirrors
16
+ * Aforo ingest's April 2026 H4 fix). Use it only when your events are
17
+ * guaranteed unique per (customer, metric, quantity, occurredAt).
18
18
  */
19
19
  function generateIdempotencyKey(customerId, metricName, quantity, occurredAt) {
20
20
  const input = `${customerId}:${metricName}:${quantity}:${occurredAt}`;
21
21
  return (0, node_crypto_1.createHash)('sha256').update(input).digest('hex').substring(0, 32);
22
22
  }
23
23
  /**
24
- * Generate a random UUID v4. This is the default key for an event whose caller
25
- * supplied none: every event gets its own key, so no two distinct events can
26
- * collide. Dedup stays opt-in via an explicit `idempotencyKey`.
24
+ * Generate a random UUID key - the automatic fallback for keyless track()
25
+ * calls. No caller key = dedup opt-out: every call is a distinct event; the
26
+ * key is stamped once at enqueue so flush retries stay dedup-safe.
27
27
  */
28
28
  function generateRandomKey() {
29
29
  return (0, node_crypto_1.randomUUID)();
@@ -1,9 +1,9 @@
1
- export { AforoClient } from './client';
2
- export { expressMiddleware, middleware } from './middleware/express';
3
- export { fastifyPlugin } from './middleware/fastify';
4
- export { koaMiddleware } from './middleware/koa';
5
- export { normalizePath } from './path-normalizer';
6
- export { DEFAULT_METRIC_NAME } from './middleware/common';
7
- export { MAX_LENGTHS, MAX_QUANTITY_DECIMAL_PLACES, MAX_QUANTITY_INTEGER_DIGITS, describeLimitViolation, } from './limits';
8
- export type { AforoOptions, TrackEvent, MiddlewareOptions, FlushResult, BatchRequest, BatchResponse, } from './types';
1
+ export { AforoClient } from './client.js';
2
+ export { expressMiddleware, middleware } from './middleware/express.js';
3
+ export { fastifyPlugin } from './middleware/fastify.js';
4
+ export { koaMiddleware } from './middleware/koa.js';
5
+ export { normalizePath } from './path-normalizer.js';
6
+ export { DEFAULT_METRIC_NAME } from './middleware/common.js';
7
+ export { MAX_LENGTHS, MAX_QUANTITY_DECIMAL_PLACES, MAX_QUANTITY_INTEGER_DIGITS, describeLimitViolation, truncateToLimit, } from './limits.js';
8
+ export type { AforoOptions, TrackEvent, MiddlewareOptions, FlushResult, BatchRequest, BatchResponse, ResolvedEvent, DropReason, } from './types.js';
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EACL,WAAW,EACX,2BAA2B,EAC3B,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,YAAY,EACZ,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,aAAa,GACd,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EACL,WAAW,EACX,2BAA2B,EAC3B,2BAA2B,EAC3B,sBAAsB,EACtB,eAAe,GAChB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,YAAY,EACZ,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,aAAa,EACb,aAAa,EACb,UAAU,GACX,MAAM,YAAY,CAAC"}
package/dist/cjs/index.js CHANGED
@@ -1,23 +1,24 @@
1
1
  "use strict";
2
2
  // Public API for @aforoai/metering
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.describeLimitViolation = exports.MAX_QUANTITY_INTEGER_DIGITS = exports.MAX_QUANTITY_DECIMAL_PLACES = exports.MAX_LENGTHS = exports.DEFAULT_METRIC_NAME = exports.normalizePath = exports.koaMiddleware = exports.fastifyPlugin = exports.middleware = exports.expressMiddleware = exports.AforoClient = void 0;
5
- var client_1 = require("./client");
6
- Object.defineProperty(exports, "AforoClient", { enumerable: true, get: function () { return client_1.AforoClient; } });
7
- var express_1 = require("./middleware/express");
8
- Object.defineProperty(exports, "expressMiddleware", { enumerable: true, get: function () { return express_1.expressMiddleware; } });
9
- Object.defineProperty(exports, "middleware", { enumerable: true, get: function () { return express_1.middleware; } });
10
- var fastify_1 = require("./middleware/fastify");
11
- Object.defineProperty(exports, "fastifyPlugin", { enumerable: true, get: function () { return fastify_1.fastifyPlugin; } });
12
- var koa_1 = require("./middleware/koa");
13
- Object.defineProperty(exports, "koaMiddleware", { enumerable: true, get: function () { return koa_1.koaMiddleware; } });
14
- var path_normalizer_1 = require("./path-normalizer");
15
- Object.defineProperty(exports, "normalizePath", { enumerable: true, get: function () { return path_normalizer_1.normalizePath; } });
16
- var common_1 = require("./middleware/common");
17
- Object.defineProperty(exports, "DEFAULT_METRIC_NAME", { enumerable: true, get: function () { return common_1.DEFAULT_METRIC_NAME; } });
18
- var limits_1 = require("./limits");
19
- Object.defineProperty(exports, "MAX_LENGTHS", { enumerable: true, get: function () { return limits_1.MAX_LENGTHS; } });
20
- Object.defineProperty(exports, "MAX_QUANTITY_DECIMAL_PLACES", { enumerable: true, get: function () { return limits_1.MAX_QUANTITY_DECIMAL_PLACES; } });
21
- Object.defineProperty(exports, "MAX_QUANTITY_INTEGER_DIGITS", { enumerable: true, get: function () { return limits_1.MAX_QUANTITY_INTEGER_DIGITS; } });
22
- Object.defineProperty(exports, "describeLimitViolation", { enumerable: true, get: function () { return limits_1.describeLimitViolation; } });
4
+ exports.truncateToLimit = exports.describeLimitViolation = exports.MAX_QUANTITY_INTEGER_DIGITS = exports.MAX_QUANTITY_DECIMAL_PLACES = exports.MAX_LENGTHS = exports.DEFAULT_METRIC_NAME = exports.normalizePath = exports.koaMiddleware = exports.fastifyPlugin = exports.middleware = exports.expressMiddleware = exports.AforoClient = void 0;
5
+ var client_js_1 = require("./client.js");
6
+ Object.defineProperty(exports, "AforoClient", { enumerable: true, get: function () { return client_js_1.AforoClient; } });
7
+ var express_js_1 = require("./middleware/express.js");
8
+ Object.defineProperty(exports, "expressMiddleware", { enumerable: true, get: function () { return express_js_1.expressMiddleware; } });
9
+ Object.defineProperty(exports, "middleware", { enumerable: true, get: function () { return express_js_1.middleware; } });
10
+ var fastify_js_1 = require("./middleware/fastify.js");
11
+ Object.defineProperty(exports, "fastifyPlugin", { enumerable: true, get: function () { return fastify_js_1.fastifyPlugin; } });
12
+ var koa_js_1 = require("./middleware/koa.js");
13
+ Object.defineProperty(exports, "koaMiddleware", { enumerable: true, get: function () { return koa_js_1.koaMiddleware; } });
14
+ var path_normalizer_js_1 = require("./path-normalizer.js");
15
+ Object.defineProperty(exports, "normalizePath", { enumerable: true, get: function () { return path_normalizer_js_1.normalizePath; } });
16
+ var common_js_1 = require("./middleware/common.js");
17
+ Object.defineProperty(exports, "DEFAULT_METRIC_NAME", { enumerable: true, get: function () { return common_js_1.DEFAULT_METRIC_NAME; } });
18
+ var limits_js_1 = require("./limits.js");
19
+ Object.defineProperty(exports, "MAX_LENGTHS", { enumerable: true, get: function () { return limits_js_1.MAX_LENGTHS; } });
20
+ Object.defineProperty(exports, "MAX_QUANTITY_DECIMAL_PLACES", { enumerable: true, get: function () { return limits_js_1.MAX_QUANTITY_DECIMAL_PLACES; } });
21
+ Object.defineProperty(exports, "MAX_QUANTITY_INTEGER_DIGITS", { enumerable: true, get: function () { return limits_js_1.MAX_QUANTITY_INTEGER_DIGITS; } });
22
+ Object.defineProperty(exports, "describeLimitViolation", { enumerable: true, get: function () { return limits_js_1.describeLimitViolation; } });
23
+ Object.defineProperty(exports, "truncateToLimit", { enumerable: true, get: function () { return limits_js_1.truncateToLimit; } });
23
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,mCAAmC;;;AAEnC,mCAAuC;AAA9B,qGAAA,WAAW,OAAA;AACpB,gDAAqE;AAA5D,4GAAA,iBAAiB,OAAA;AAAE,qGAAA,UAAU,OAAA;AACtC,gDAAqD;AAA5C,wGAAA,aAAa,OAAA;AACtB,wCAAiD;AAAxC,oGAAA,aAAa,OAAA;AACtB,qDAAkD;AAAzC,gHAAA,aAAa,OAAA;AACtB,8CAA0D;AAAjD,6GAAA,mBAAmB,OAAA;AAC5B,mCAKkB;AAJhB,qGAAA,WAAW,OAAA;AACX,qHAAA,2BAA2B,OAAA;AAC3B,qHAAA,2BAA2B,OAAA;AAC3B,gHAAA,sBAAsB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,mCAAmC;;;AAEnC,yCAA0C;AAAjC,wGAAA,WAAW,OAAA;AACpB,sDAAwE;AAA/D,+GAAA,iBAAiB,OAAA;AAAE,wGAAA,UAAU,OAAA;AACtC,sDAAwD;AAA/C,2GAAA,aAAa,OAAA;AACtB,8CAAoD;AAA3C,uGAAA,aAAa,OAAA;AACtB,2DAAqD;AAA5C,mHAAA,aAAa,OAAA;AACtB,oDAA6D;AAApD,gHAAA,mBAAmB,OAAA;AAC5B,yCAMqB;AALnB,wGAAA,WAAW,OAAA;AACX,wHAAA,2BAA2B,OAAA;AAC3B,wHAAA,2BAA2B,OAAA;AAC3B,mHAAA,sBAAsB,OAAA;AACtB,4GAAA,eAAe,OAAA"}