node-avo-inspector 2.0.0-beta.1 → 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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## 2.0.0
3
+ ## 2.0.0 (2026-10-22)
4
4
 
5
5
  Implements [avohq/spec-first-inspector-server-sdk](https://github.com/avohq/spec-first-inspector-server-sdk) v3.0.1: the `/inspector/v2/track` endpoint, batching with `flush()` and `destroy()`, gzip, and gateway fields. This is a breaking release; see [Upgrading from 1.x to 2.0](README.md#upgrading-from-1x-to-20) in the README.
6
6
 
package/README.md CHANGED
@@ -318,6 +318,8 @@ To test against a local mock server, set the `AVO_INSPECTOR_MOCK_ENDPOINT` envir
318
318
 
319
319
  Update the `VERSION` constant in `src/AvoInspectorVersion.ts` and the `version` in `package.json` on every release. `VERSION` is sent to Avo as `libVersion`, and a unit test fails if the two differ.
320
320
 
321
+ `npm pack` and `npm publish` run the `prepack` script, which deletes `dist/` and rebuilds it from `src/`, so a stale local `dist/` (it is git-ignored) is not packed. This needs npm lifecycle scripts enabled: with `ignore-scripts` set, `prepack` is skipped and whatever is in `dist/` is packed. Before publishing, run `npm pack --dry-run` and check that it prints the `prepack` build and lists the files you expect.
322
+
321
323
  ## Conformance suite
322
324
 
323
325
  `scripts/run-conformance.sh` builds the SDK and runs the spec's conformance suite (36 fixtures) against the harness in `conformance/avo-inspector-conformance.js` (runner contract 1.1.0). It fetches the spec at the pinned commit into `.spec-repo/`; set `SPEC_DIR` to use a local checkout instead.
@@ -0,0 +1,73 @@
1
+ import { InspectorBody } from "./AvoNetworkCallsHandler";
2
+ export declare const MAX_TIMER_MS = 2147483647;
3
+ export declare const MAX_IN_FLIGHT_SENDS = 4;
4
+ export declare const MAX_WAITING_EVENTS = 10000;
5
+ export declare const BACKPRESSURE_WAITING_EVENTS = 1000;
6
+ export declare const BACKPRESSURE_MAX_WAIT_MS = 10000;
7
+ export interface AvoBatchOptions {
8
+ batchSize: number;
9
+ batchFlushSeconds: number;
10
+ maxQueueSize: number;
11
+ disableBatchTimer: boolean;
12
+ }
13
+ /**
14
+ * In-memory pending batch buffer. Node runs this on a single thread, so appending and
15
+ * the swap-and-clear in drain() are atomic without a lock; the send itself happens in
16
+ * the dispatch callback, after the buffer has been swapped out. At most
17
+ * MAX_IN_FLIGHT_SENDS dispatches run at once; later batches wait in order.
18
+ */
19
+ export declare class AvoBatchQueue<T> {
20
+ private options;
21
+ private dispatch;
22
+ private dropped;
23
+ private track;
24
+ private buffer;
25
+ private waiting;
26
+ private waitingEvents;
27
+ private inFlight;
28
+ private inFlightEventCount;
29
+ private capacityWaiters;
30
+ private capacityTimer;
31
+ private flushTimer;
32
+ private bufferOutcome;
33
+ /**
34
+ * @param dispatch sends one batch; must not reject.
35
+ * @param dropped the outcome for a batch discarded before it was sent.
36
+ * @param track called with each batch's outcome promise as soon as it is swapped out.
37
+ */
38
+ constructor(options: AvoBatchOptions, dispatch: (batch: Array<InspectorBody>) => Promise<T>, dropped: T, track?: (outcome: Promise<T>) => Promise<T>);
39
+ /** Events in the unsent buffer (bounded by maxQueueSize). */
40
+ get length(): number;
41
+ /** Events in batches waiting for a send slot (bounded by MAX_WAITING_EVENTS). */
42
+ get waitingLength(): number;
43
+ /** Events in batches being sent. */
44
+ get inFlightEvents(): number;
45
+ /**
46
+ * Appends an event and evaluates the size trigger. Returns the send promise when
47
+ * this call dispatched a batch, otherwise null.
48
+ */
49
+ enqueue(event: InspectorBody): Promise<T> | null;
50
+ /**
51
+ * The outcome of the batch that will carry the events buffered now, whichever drain (size
52
+ * trigger, timer, flush) swaps them out. Null when the buffer is empty.
53
+ */
54
+ bufferedBatchOutcome(): Promise<T> | null;
55
+ /** Swaps out every buffered event and dispatches them as one batch. */
56
+ drain(): Promise<T> | null;
57
+ /**
58
+ * Resolves once fewer than BACKPRESSURE_WAITING_EVENTS events wait for a send slot (at
59
+ * once if they already do), when the backlog overflows, and at the latest after
60
+ * BACKPRESSURE_MAX_WAIT_MS. clear() resolves it too.
61
+ */
62
+ whenBelowBackpressure(): Promise<void>;
63
+ /** Tracks waiting on backpressure. */
64
+ get capacityWaiterCount(): number;
65
+ private releaseCapacityWaiters;
66
+ /** Discards every buffered or waiting event unsent and cancels the scheduled flush. */
67
+ clear(): void;
68
+ private startSends;
69
+ private dropOldestWaiting;
70
+ private scheduleFlush;
71
+ private clearTimer;
72
+ get hasScheduledFlush(): boolean;
73
+ }
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AvoBatchQueue = exports.BACKPRESSURE_MAX_WAIT_MS = exports.BACKPRESSURE_WAITING_EVENTS = exports.MAX_WAITING_EVENTS = exports.MAX_IN_FLIGHT_SENDS = exports.MAX_TIMER_MS = void 0;
4
+ const AvoLog_1 = require("./AvoLog");
5
+ // setTimeout fires almost at once for delays above this (2^31 - 1 ms, about 24.8 days).
6
+ exports.MAX_TIMER_MS = 2147483647;
7
+ // Batch sends in flight at once. Batches swapped out beyond this wait for a free slot, so
8
+ // a fast producer or a slow endpoint cannot open an unbounded number of requests.
9
+ exports.MAX_IN_FLIGHT_SENDS = 4;
10
+ // Events that may wait for a send slot, across all waiting batches. Separate from
11
+ // maxQueueSize, which bounds only the unsent buffer; past this the oldest waiting events
12
+ // are dropped.
13
+ exports.MAX_WAITING_EVENTS = 10000;
14
+ // Backpressure: from this many events waiting for a send slot, an awaited track waits until
15
+ // fewer wait (see whenBelowBackpressure), so awaited loops slow to the speed of the sends.
16
+ exports.BACKPRESSURE_WAITING_EVENTS = 1000;
17
+ // The longest a track waits on backpressure: one request timeout.
18
+ exports.BACKPRESSURE_MAX_WAIT_MS = 10000;
19
+ /**
20
+ * In-memory pending batch buffer. Node runs this on a single thread, so appending and
21
+ * the swap-and-clear in drain() are atomic without a lock; the send itself happens in
22
+ * the dispatch callback, after the buffer has been swapped out. At most
23
+ * MAX_IN_FLIGHT_SENDS dispatches run at once; later batches wait in order.
24
+ */
25
+ class AvoBatchQueue {
26
+ /**
27
+ * @param dispatch sends one batch; must not reject.
28
+ * @param dropped the outcome for a batch discarded before it was sent.
29
+ * @param track called with each batch's outcome promise as soon as it is swapped out.
30
+ */
31
+ constructor(options, dispatch, dropped, track = (outcome) => outcome) {
32
+ this.options = options;
33
+ this.dispatch = dispatch;
34
+ this.dropped = dropped;
35
+ this.track = track;
36
+ this.buffer = [];
37
+ this.waiting = [];
38
+ this.waitingEvents = 0;
39
+ this.inFlight = 0;
40
+ this.inFlightEventCount = 0;
41
+ // Resolved once fewer than BACKPRESSURE_WAITING_EVENTS events wait, when the backlog
42
+ // overflows, when the wait timer fires, or by clear().
43
+ this.capacityWaiters = [];
44
+ // Ref'd while there are waiters: a stalled awaited loop is pending work, so "beforeExit"
45
+ // does not fire in the middle of it. Fires after BACKPRESSURE_MAX_WAIT_MS at the latest.
46
+ this.capacityTimer = null;
47
+ this.flushTimer = null;
48
+ // Settled with the outcome of the batch that takes the current buffer, whichever drain
49
+ // swaps it out (or `dropped` if clear() discards it). Created on demand.
50
+ this.bufferOutcome = null;
51
+ }
52
+ /** Events in the unsent buffer (bounded by maxQueueSize). */
53
+ get length() {
54
+ return this.buffer.length;
55
+ }
56
+ /** Events in batches waiting for a send slot (bounded by MAX_WAITING_EVENTS). */
57
+ get waitingLength() {
58
+ return this.waitingEvents;
59
+ }
60
+ /** Events in batches being sent. */
61
+ get inFlightEvents() {
62
+ return this.inFlightEventCount;
63
+ }
64
+ /**
65
+ * Appends an event and evaluates the size trigger. Returns the send promise when
66
+ * this call dispatched a batch, otherwise null.
67
+ */
68
+ enqueue(event) {
69
+ this.buffer.push(event);
70
+ const overflow = this.buffer.length - this.options.maxQueueSize;
71
+ if (overflow > 0) {
72
+ this.buffer.splice(0, overflow);
73
+ AvoLog_1.AvoLog.dropped(overflow, "queue full");
74
+ }
75
+ if (this.buffer.length >= this.options.batchSize) {
76
+ return this.drain();
77
+ }
78
+ this.scheduleFlush();
79
+ return null;
80
+ }
81
+ /**
82
+ * The outcome of the batch that will carry the events buffered now, whichever drain (size
83
+ * trigger, timer, flush) swaps them out. Null when the buffer is empty.
84
+ */
85
+ bufferedBatchOutcome() {
86
+ if (this.buffer.length === 0) {
87
+ return null;
88
+ }
89
+ if (this.bufferOutcome === null) {
90
+ let settle;
91
+ const promise = new Promise((resolve) => (settle = resolve));
92
+ this.bufferOutcome = { promise, settle };
93
+ }
94
+ return this.bufferOutcome.promise;
95
+ }
96
+ /** Swaps out every buffered event and dispatches them as one batch. */
97
+ drain() {
98
+ this.clearTimer();
99
+ if (this.buffer.length === 0) {
100
+ return null;
101
+ }
102
+ const batch = this.buffer;
103
+ this.buffer = [];
104
+ const outcome = new Promise((settle) => {
105
+ this.waiting.push({ events: batch, settle });
106
+ });
107
+ if (this.bufferOutcome !== null) {
108
+ this.bufferOutcome.settle(outcome);
109
+ this.bufferOutcome = null;
110
+ }
111
+ this.waitingEvents += batch.length;
112
+ this.startSends();
113
+ const excess = this.waitingEvents - exports.MAX_WAITING_EVENTS;
114
+ if (excess > 0) {
115
+ this.dropOldestWaiting(excess);
116
+ AvoLog_1.AvoLog.dropped(excess, "send backlog full");
117
+ // An awaited loop stalls near BACKPRESSURE_WAITING_EVENTS, so an overflow means the
118
+ // callers are not awaiting: release them all. Calls that are not awaited therefore
119
+ // hold at most the waiters registered since the last overflow (under one batch),
120
+ // while an awaited loop that reaches the cap is still throttled between overflows.
121
+ this.releaseCapacityWaiters(true);
122
+ }
123
+ return this.track(outcome);
124
+ }
125
+ /**
126
+ * Resolves once fewer than BACKPRESSURE_WAITING_EVENTS events wait for a send slot (at
127
+ * once if they already do), when the backlog overflows, and at the latest after
128
+ * BACKPRESSURE_MAX_WAIT_MS. clear() resolves it too.
129
+ */
130
+ whenBelowBackpressure() {
131
+ if (this.waitingEvents < exports.BACKPRESSURE_WAITING_EVENTS) {
132
+ return Promise.resolve();
133
+ }
134
+ return new Promise((resolve) => {
135
+ this.capacityWaiters.push(resolve);
136
+ if (this.capacityTimer === null) {
137
+ this.capacityTimer = setTimeout(() => {
138
+ this.capacityTimer = null;
139
+ this.releaseCapacityWaiters(true);
140
+ }, exports.BACKPRESSURE_MAX_WAIT_MS);
141
+ }
142
+ });
143
+ }
144
+ /** Tracks waiting on backpressure. */
145
+ get capacityWaiterCount() {
146
+ return this.capacityWaiters.length;
147
+ }
148
+ releaseCapacityWaiters(all = false) {
149
+ if (!all && this.waitingEvents >= exports.BACKPRESSURE_WAITING_EVENTS) {
150
+ return;
151
+ }
152
+ if (this.capacityTimer !== null) {
153
+ clearTimeout(this.capacityTimer);
154
+ this.capacityTimer = null;
155
+ }
156
+ const waiters = this.capacityWaiters;
157
+ this.capacityWaiters = [];
158
+ waiters.forEach((resolve) => resolve());
159
+ }
160
+ /** Discards every buffered or waiting event unsent and cancels the scheduled flush. */
161
+ clear() {
162
+ this.clearTimer();
163
+ this.buffer = [];
164
+ if (this.bufferOutcome !== null) {
165
+ this.bufferOutcome.settle(this.dropped);
166
+ this.bufferOutcome = null;
167
+ }
168
+ const discarded = this.waiting;
169
+ this.waiting = [];
170
+ this.waitingEvents = 0;
171
+ discarded.forEach((batch) => batch.settle(this.dropped));
172
+ this.releaseCapacityWaiters(true);
173
+ }
174
+ startSends() {
175
+ while (this.inFlight < exports.MAX_IN_FLIGHT_SENDS && this.waiting.length > 0) {
176
+ const batch = this.waiting.shift();
177
+ this.waitingEvents -= batch.events.length;
178
+ this.inFlight++;
179
+ this.inFlightEventCount += batch.events.length;
180
+ const done = (outcome) => {
181
+ this.inFlight--;
182
+ this.inFlightEventCount -= batch.events.length;
183
+ batch.settle(outcome);
184
+ this.startSends();
185
+ };
186
+ // A dispatch that throws or rejects is an internal error: its events are lost, so both
187
+ // are reported. The slot is still freed and the batch still settles.
188
+ const failedInternally = (err) => {
189
+ AvoLog_1.AvoLog.internal(err);
190
+ AvoLog_1.AvoLog.dropped(batch.events.length, "internal error");
191
+ done(this.dropped);
192
+ };
193
+ let sent;
194
+ try {
195
+ sent = this.dispatch(batch.events);
196
+ }
197
+ catch (err) {
198
+ // Settled asynchronously, like any other dispatch outcome.
199
+ sent = Promise.reject(err);
200
+ }
201
+ sent.then(done, failedInternally);
202
+ }
203
+ this.releaseCapacityWaiters();
204
+ }
205
+ dropOldestWaiting(count) {
206
+ while (count > 0 && this.waiting.length > 0) {
207
+ const batch = this.waiting[0];
208
+ const removed = batch.events.splice(0, Math.min(count, batch.events.length)).length;
209
+ this.waitingEvents -= removed;
210
+ count -= removed;
211
+ if (batch.events.length === 0) {
212
+ this.waiting.shift();
213
+ batch.settle(this.dropped);
214
+ }
215
+ }
216
+ }
217
+ // Fires once the oldest buffered event is batchFlushSeconds old. Unref'd so it never
218
+ // holds the process open.
219
+ scheduleFlush() {
220
+ if (this.options.disableBatchTimer || this.flushTimer !== null) {
221
+ return;
222
+ }
223
+ this.flushTimer = setTimeout(() => {
224
+ this.flushTimer = null;
225
+ this.drain();
226
+ }, Math.min(this.options.batchFlushSeconds * 1000, exports.MAX_TIMER_MS));
227
+ this.flushTimer.unref();
228
+ }
229
+ clearTimer() {
230
+ if (this.flushTimer !== null) {
231
+ clearTimeout(this.flushTimer);
232
+ this.flushTimer = null;
233
+ }
234
+ }
235
+ get hasScheduledFlush() {
236
+ return this.flushTimer !== null;
237
+ }
238
+ }
239
+ exports.AvoBatchQueue = AvoBatchQueue;
@@ -1,11 +1,18 @@
1
+ interface Registration {
2
+ time: number;
3
+ key: string;
4
+ generation: number;
5
+ }
1
6
  export declare class AvoDeduplicator {
2
- avoFunctionsEvents: {
3
- [time: number]: string;
4
- };
5
- manualEvents: {
6
- [time: number]: string;
7
- };
7
+ avoFunctionsEvents: Array<Registration>;
8
+ manualEvents: Array<Registration>;
9
+ private avoFunctionsHead;
10
+ private manualHead;
8
11
  private msToConsiderOld;
12
+ private now;
13
+ private avoFunctionsLatest;
14
+ private manualLatest;
15
+ private nextGeneration;
9
16
  avoFunctionsEventsParams: {
10
17
  [key: string]: {
11
18
  [propName: string]: any;
@@ -27,5 +34,7 @@ export declare class AvoDeduplicator {
27
34
  }, checkInAvoFunctions: boolean): boolean;
28
35
  private lookForEventParamsIn;
29
36
  private clearOldEvents;
37
+ private expire;
30
38
  private _clearEvents;
31
39
  }
40
+ export {};
@@ -4,9 +4,21 @@ exports.AvoDeduplicator = void 0;
4
4
  const utils_1 = require("./utils");
5
5
  class AvoDeduplicator {
6
6
  constructor() {
7
- this.avoFunctionsEvents = {};
8
- this.manualEvents = {};
7
+ // Every registration, oldest first; `head` is the first one not yet expired. Cleanup pops
8
+ // from the front, so its cost is amortised O(1) per call.
9
+ this.avoFunctionsEvents = [];
10
+ this.manualEvents = [];
11
+ this.avoFunctionsHead = 0;
12
+ this.manualHead = 0;
9
13
  this.msToConsiderOld = 500;
14
+ // Milliseconds from a monotonic clock: the log must stay in time order, and a wall clock
15
+ // stepping back (NTP, manual change) would stall expiry. Replaceable in tests.
16
+ this.now = utils_1.monotonicNowMs;
17
+ // The generation of each key's latest registration. A key's params always belong to its
18
+ // latest registration, so only that registration's expiry may delete them.
19
+ this.avoFunctionsLatest = {};
20
+ this.manualLatest = {};
21
+ this.nextGeneration = 0;
10
22
  // Keyed by streamId\0eventName to prevent cross-stream suppression on server
11
23
  this.avoFunctionsEventsParams = {};
12
24
  this.manualEventsParams = {};
@@ -17,12 +29,15 @@ class AvoDeduplicator {
17
29
  shouldRegisterEvent(eventName, params, fromAvoFunction, streamId = "") {
18
30
  this.clearOldEvents();
19
31
  const key = AvoDeduplicator.dedupKey(eventName, streamId);
32
+ const registration = { time: this.now(), key, generation: this.nextGeneration++ };
20
33
  if (fromAvoFunction) {
21
- this.avoFunctionsEvents[Date.now()] = key;
34
+ this.avoFunctionsEvents.push(registration);
35
+ this.avoFunctionsLatest[key] = registration.generation;
22
36
  this.avoFunctionsEventsParams[key] = params;
23
37
  }
24
38
  else {
25
- this.manualEvents[Date.now()] = key;
39
+ this.manualEvents.push(registration);
40
+ this.manualLatest[key] = registration.generation;
26
41
  this.manualEventsParams[key] = params;
27
42
  }
28
43
  let checkInAvoFunctions = !fromAvoFunction;
@@ -81,34 +96,41 @@ class AvoDeduplicator {
81
96
  return false;
82
97
  }
83
98
  clearOldEvents() {
84
- const now = Date.now();
85
- for (const time in this.avoFunctionsEvents) {
86
- if (this.avoFunctionsEvents.hasOwnProperty(time)) {
87
- const timestamp = Number(time) || 0;
88
- if (now - timestamp > this.msToConsiderOld) {
89
- const key = this.avoFunctionsEvents[time];
90
- delete this.avoFunctionsEvents[time];
91
- delete this.avoFunctionsEventsParams[key];
92
- }
93
- }
99
+ const now = this.now();
100
+ this.avoFunctionsHead = this.expire(this.avoFunctionsEvents, this.avoFunctionsHead, this.avoFunctionsEventsParams, this.avoFunctionsLatest, now);
101
+ this.manualHead = this.expire(this.manualEvents, this.manualHead, this.manualEventsParams, this.manualLatest, now);
102
+ if (this.avoFunctionsHead > 1024 && this.avoFunctionsHead * 2 > this.avoFunctionsEvents.length) {
103
+ this.avoFunctionsEvents = this.avoFunctionsEvents.slice(this.avoFunctionsHead);
104
+ this.avoFunctionsHead = 0;
94
105
  }
95
- for (const time in this.manualEvents) {
96
- if (this.manualEvents.hasOwnProperty(time)) {
97
- const timestamp = Number(time) || 0;
98
- if (now - timestamp > this.msToConsiderOld) {
99
- const key = this.manualEvents[time];
100
- delete this.manualEvents[time];
101
- delete this.manualEventsParams[key];
102
- }
106
+ if (this.manualHead > 1024 && this.manualHead * 2 > this.manualEvents.length) {
107
+ this.manualEvents = this.manualEvents.slice(this.manualHead);
108
+ this.manualHead = 0;
109
+ }
110
+ }
111
+ // Pops every registration older than msToConsiderOld and deletes a key's params only when
112
+ // the popped registration is still that key's latest; returns the new head.
113
+ expire(registrations, head, paramsByKey, latestByKey, now) {
114
+ while (head < registrations.length && now - registrations[head].time > this.msToConsiderOld) {
115
+ const { key, generation } = registrations[head];
116
+ if (latestByKey[key] === generation) {
117
+ delete paramsByKey[key];
118
+ delete latestByKey[key];
103
119
  }
120
+ head++;
104
121
  }
122
+ return head;
105
123
  }
106
124
  // used in tests
107
125
  _clearEvents() {
108
- this.avoFunctionsEvents = {};
109
- this.manualEvents = {};
126
+ this.avoFunctionsEvents = [];
127
+ this.manualEvents = [];
128
+ this.avoFunctionsHead = 0;
129
+ this.manualHead = 0;
110
130
  this.avoFunctionsEventsParams = {};
111
131
  this.manualEventsParams = {};
132
+ this.avoFunctionsLatest = {};
133
+ this.manualLatest = {};
112
134
  }
113
135
  }
114
136
  exports.AvoDeduplicator = AvoDeduplicator;
@@ -1,6 +1,41 @@
1
1
  import { AvoInspectorEnvValueType } from "./AvoInspectorEnv";
2
2
  import { AvoNetworkCallsHandler } from "./AvoNetworkCallsHandler";
3
3
  import { AvoDeduplicator } from "./AvoDeduplicator";
4
+ /**
5
+ * One observed event, the single argument of `trackSchemaFromEvent`. The gateway fields
6
+ * are for a gateway-scoped Inspector API key; blank values are treated as absent.
7
+ */
8
+ export interface InspectorEvent {
9
+ /** The event name. A missing, empty or whitespace-only name is sent as "Missing Event Name". */
10
+ eventName: string;
11
+ /** The event's properties. Absent or null: an event with no properties. */
12
+ eventProperties?: {
13
+ [propName: string]: any;
14
+ } | null;
15
+ /** Optional stream id to correlate events. */
16
+ streamId?: string;
17
+ /** Reference of the gateway output this observation was bound for. Absent = gateway checkpoint. */
18
+ outputReference?: string;
19
+ /** Low-cardinality label of the source the event came from (e.g. "web"). Never a user id. */
20
+ originHint?: string;
21
+ /** App version of the source that produced this event; overrides the instance version. */
22
+ originAppVersion?: string;
23
+ }
24
+ /**
25
+ * The gateway fields of a Codegen call (`_avoFunctionTrackSchemaFromEvent`), with the same
26
+ * meaning as on InspectorEvent. Not re-exported from the package root, but not marked internal:
27
+ * stripInternal would drop it from the declarations while that public method still uses it.
28
+ */
29
+ export interface CodegenGatewayFields {
30
+ outputReference?: string;
31
+ originHint?: string;
32
+ originAppVersion?: string;
33
+ }
34
+ type SchemaEntry = {
35
+ propertyName: string;
36
+ propertyType: string;
37
+ children?: any;
38
+ };
4
39
  export declare class AvoInspector {
5
40
  environment: AvoInspectorEnvValueType;
6
41
  avoNetworkCallsHandler: AvoNetworkCallsHandler;
@@ -12,45 +47,116 @@ export declare class AvoInspector {
12
47
  private eventSpecCache;
13
48
  private eventValidator;
14
49
  private generatedAnonymousId;
15
- private pendingCount;
16
- private keepAliveTimer;
50
+ private batchSize;
51
+ private batchQueue;
52
+ private destroyed;
53
+ private destroyWaiters;
54
+ private pending;
55
+ private validations;
56
+ private drainScheduled;
57
+ private drainThisTurn;
58
+ private untilDestroyed;
59
+ private untilGivenUp;
60
+ /** The exit drain ran out of time for validations: their events are sent unvalidated. */
61
+ private giveUpValidations;
62
+ private get pendingCount();
17
63
  private trackPending;
64
+ private static instancesWithWork;
65
+ private static exitDrainArmed;
66
+ private static exitReportArmed;
67
+ private static exitDeadline;
68
+ private static waitingValidations;
69
+ private releaseWaiting;
70
+ private validationOutcomes;
71
+ private giveUpWaiters;
72
+ private static lastTrackAt;
73
+ private static validationGraceUsedFor;
74
+ private static explicitFlushDeadlines;
75
+ private static drainOnExit;
76
+ private static reportOnExit;
77
+ private static armExitDrain;
78
+ private static disarmExitDrain;
79
+ private updateExitDrain;
18
80
  private static _shouldLog;
81
+ /** Whether the SDK writes its diagnostic log lines (shared by every instance). */
19
82
  static get shouldLog(): boolean;
20
83
  static set shouldLog(enable: boolean);
84
+ /**
85
+ * Creates an Inspector instance. A missing or unsupported `env` falls back to dev.
86
+ *
87
+ * @throws if `apiKey` or `version` is blank, or `apiKey` contains a character that
88
+ * cannot be sent in an HTTP header.
89
+ */
21
90
  constructor(options: {
22
91
  apiKey: string;
23
92
  env: AvoInspectorEnvValueType;
24
93
  version: string;
25
94
  appName?: string;
26
95
  publicEncryptionKey?: string;
96
+ /** Flush when this many events are buffered. Default 30; always 1 in dev. */
97
+ batchSize?: number;
98
+ /** Flush once the oldest buffered event is this many seconds old. Default 30. */
99
+ batchFlushSeconds?: number;
100
+ /** Maximum buffered events; the oldest are dropped first. Default 1000. */
101
+ maxQueueSize?: number;
102
+ /** Start no background flush timer (recommended for serverless). Default false. */
103
+ disableBatchTimer?: boolean;
27
104
  });
28
- trackSchemaFromEvent(eventName: string, eventProperties: {
29
- [propName: string]: any;
30
- }, streamId?: string): Promise<Array<{
31
- propertyName: string;
32
- propertyType: string;
33
- children?: any;
34
- }>>;
105
+ private static resolveBatchOptions;
106
+ /**
107
+ * Resolves the gateway options for one event. outputReference / originHint are
108
+ * omitted when blank; appVersion is the per-event override, null for a
109
+ * source-scoped event without one, else the instance version.
110
+ */
111
+ private resolveGatewayFields;
112
+ private static warnUnknownEventKeys;
113
+ /**
114
+ * Extracts the event schema and queues it for the Inspector API.
115
+ *
116
+ * Resolves with the schema once the event is queued. In dev (batch size 1) the send
117
+ * happens within the call and a non-200 response resolves `[]`; with batching the
118
+ * batch's HTTP outcome is not observable here. Buffered events are only delivered
119
+ * by a later size or time trigger, or by `flush()` — call `flush()` before exit.
120
+ *
121
+ * Anything but an object (such as a 1.x call `trackSchemaFromEvent(name, properties)`)
122
+ * sends nothing, prints an always-on error naming the fix, and resolves `[]`.
123
+ */
124
+ trackSchemaFromEvent(event: InspectorEvent): Promise<Array<SchemaEntry>>;
125
+ /**
126
+ * Codegen entry point: like `trackSchemaFromEvent`, and additionally records the
127
+ * Avo function's `eventId` / `eventHash` so the event is sent with `avoFunction: true`.
128
+ */
35
129
  _avoFunctionTrackSchemaFromEvent(eventName: string, eventProperties: {
36
130
  [propName: string]: any;
37
- }, eventId: string, eventHash: string, streamId?: string): Promise<Array<{
38
- propertyName: string;
39
- propertyType: string;
40
- children?: any;
41
- }>>;
131
+ }, eventId: string, eventHash: string, streamId?: string, options?: CodegenGatewayFields): Promise<Array<SchemaEntry>>;
132
+ private track;
42
133
  /**
43
- * Try to fetch event spec and validate, then send a single event call.
44
- * If validation succeeds, sends a validated event (with results merged in).
45
- * If unavailable, sends a plain event.
46
- *
47
- * Uses trackPending() to keep the Node process alive until the work completes,
48
- * so short-lived processes (CLIs) don't exit before the send finishes — even
49
- * when the caller doesn't await the returned promise.
134
+ * Codegen/manual deduplication applies only to plain calls. A call carrying gateway
135
+ * options is a distinct observation per gateway output and is always sent.
136
+ */
137
+ private shouldRegisterEvent;
138
+ /**
139
+ * Samples the event, then queues it. When event spec validation is active the spec
140
+ * is fetched (or read from cache) first, so the event joins the queue once its
141
+ * validation results are merged in; flush() awaits that fetch before draining.
50
142
  */
51
- private sendEventWithOptionalValidation;
52
- private doSendEventWithOptionalValidation;
143
+ private sampleAndEnqueue;
144
+ private enqueue;
145
+ private sendBatch;
146
+ private isValidationActive;
147
+ /**
148
+ * Sends every buffered event, then waits until all in-flight sends (and spec fetches
149
+ * that will enqueue) have completed or `timeoutMs` (default 10000) has elapsed.
150
+ * Resolves `true` if, when it resolves, this instance has nothing buffered, waiting or in
151
+ * flight (always after destroy()), and `false` if the timeout won. Never rejects. Call it
152
+ * before process exit or before a serverless handler returns.
153
+ */
154
+ flush(timeoutMs?: number): Promise<boolean>;
155
+ private hasDrained;
156
+ private flushWithin;
157
+ /** Turns the SDK's diagnostic logging on or off for every instance. */
53
158
  enableLogging(enable: boolean): void;
159
+ /** Returns the schema of `eventProperties` without tracking or sending anything. */
54
160
  extractSchema(eventProperties: {
55
161
  [propName: string]: any;
56
162
  }, shouldLogIfEnabled?: boolean): Array<{
@@ -63,5 +169,12 @@ export declare class AvoInspector {
63
169
  * validation succeeds, or null if unavailable (prod, no spec, fetch error).
64
170
  */
65
171
  private fetchAndValidate;
172
+ /**
173
+ * Cancels and cleans up: discards the pending batch unsent, abandons in-flight
174
+ * requests and stops the flush timer. Does not flush. After destroy() the instance
175
+ * is terminated and trackSchemaFromEvent resolves [] without sending; a call still
176
+ * waiting on a spec fetch or on its immediate send also resolves [].
177
+ */
66
178
  destroy(): void;
67
179
  }
180
+ export {};