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