node-avo-inspector 1.2.0 → 2.0.0-beta.2

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 (132) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +270 -4
  3. package/dist/AvoBatchQueue.d.ts +73 -0
  4. package/dist/AvoBatchQueue.js +239 -0
  5. package/dist/AvoDeduplicator.d.ts +15 -6
  6. package/dist/AvoDeduplicator.js +46 -24
  7. package/dist/AvoInspector.d.ts +136 -23
  8. package/dist/AvoInspector.js +777 -109
  9. package/dist/AvoInspectorVersion.d.ts +3 -0
  10. package/dist/AvoInspectorVersion.js +10 -0
  11. package/dist/AvoLog.d.ts +52 -0
  12. package/dist/AvoLog.js +246 -0
  13. package/dist/AvoNetworkCallsHandler.d.ts +55 -8
  14. package/dist/AvoNetworkCallsHandler.js +266 -92
  15. package/dist/AvoSchemaParser.d.ts +6 -0
  16. package/dist/AvoSchemaParser.js +138 -33
  17. package/dist/AvoStreamId.d.ts +2 -1
  18. package/dist/AvoStreamId.js +21 -2
  19. package/dist/eventSpec/AvoEventSpecCache.d.ts +28 -5
  20. package/dist/eventSpec/AvoEventSpecCache.js +66 -11
  21. package/dist/eventSpec/AvoEventSpecFetchTypes.d.ts +50 -18
  22. package/dist/eventSpec/AvoEventSpecFetcher.d.ts +29 -6
  23. package/dist/eventSpec/AvoEventSpecFetcher.js +279 -69
  24. package/dist/eventSpec/EventValidator.d.ts +16 -10
  25. package/dist/eventSpec/EventValidator.js +351 -38
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +4 -1
  28. package/dist/utils.d.ts +29 -1
  29. package/dist/utils.js +86 -10
  30. package/package.json +18 -1
  31. package/.claude/scheduled_tasks.lock +0 -1
  32. package/.claude/settings.local.json +0 -11
  33. package/.claude/worktrees/release-1.1.1/LICENSE +0 -21
  34. package/.claude/worktrees/release-1.1.1/README.md +0 -69
  35. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/index.js +0 -30
  36. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/package.json +0 -15
  37. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/yarn.lock +0 -6
  38. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/dist/index.js +0 -34
  39. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/index.ts +0 -30
  40. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/package.json +0 -18
  41. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/tsconfig.json +0 -15
  42. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/yarn.lock +0 -11
  43. package/.claude/worktrees/release-1.1.1/package-lock.json +0 -4039
  44. package/.claude/worktrees/release-1.1.1/package.json +0 -48
  45. package/.claude/worktrees/release-1.1.1/reason/AvoInspector.rei +0 -11
  46. package/.claude/worktrees/release-1.1.1/reason/NodeAvoInspector.re +0 -27
  47. package/.claude/worktrees/release-1.1.1/src/AvoDeduplicator.ts +0 -146
  48. package/.claude/worktrees/release-1.1.1/src/AvoEncryption.ts +0 -90
  49. package/.claude/worktrees/release-1.1.1/src/AvoGuid.ts +0 -9
  50. package/.claude/worktrees/release-1.1.1/src/AvoInspector.ts +0 -383
  51. package/.claude/worktrees/release-1.1.1/src/AvoInspectorEnv.ts +0 -8
  52. package/.claude/worktrees/release-1.1.1/src/AvoNetworkCallsHandler.ts +0 -267
  53. package/.claude/worktrees/release-1.1.1/src/AvoSchemaParser.ts +0 -115
  54. package/.claude/worktrees/release-1.1.1/src/AvoStreamId.ts +0 -16
  55. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEncryption.ts +0 -515
  56. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecCache.ts +0 -242
  57. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecFetcher.ts +0 -241
  58. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoInspector.ts +0 -360
  59. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoStreamId.ts +0 -72
  60. package/.claude/worktrees/release-1.1.1/src/__tests__/TestDeduplicator.ts +0 -191
  61. package/.claude/worktrees/release-1.1.1/src/__tests__/TestEventValidator.ts +0 -231
  62. package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandler.ts +0 -179
  63. package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandlerPublic.ts +0 -208
  64. package/.claude/worktrees/release-1.1.1/src/__tests__/TestParsing.ts +0 -153
  65. package/.claude/worktrees/release-1.1.1/src/__tests__/constants.ts +0 -63
  66. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecCache.ts +0 -109
  67. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetchTypes.ts +0 -47
  68. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetcher.ts +0 -106
  69. package/.claude/worktrees/release-1.1.1/src/eventSpec/EventValidator.ts +0 -74
  70. package/.claude/worktrees/release-1.1.1/src/index.ts +0 -2
  71. package/.claude/worktrees/release-1.1.1/src/utils.ts +0 -49
  72. package/.claude/worktrees/release-1.1.1/tsconfig.json +0 -13
  73. package/.claude/worktrees/release-1.1.1/yarn.lock +0 -2310
  74. package/.github/workflows/claude.yml +0 -55
  75. package/dist/__tests__/TestAvoEncryption.d.ts +0 -1
  76. package/dist/__tests__/TestAvoEncryption.js +0 -288
  77. package/dist/__tests__/TestAvoEventSpecCache.d.ts +0 -1
  78. package/dist/__tests__/TestAvoEventSpecCache.js +0 -194
  79. package/dist/__tests__/TestAvoEventSpecFetcher.d.ts +0 -1
  80. package/dist/__tests__/TestAvoEventSpecFetcher.js +0 -219
  81. package/dist/__tests__/TestAvoInspector.d.ts +0 -1
  82. package/dist/__tests__/TestAvoInspector.js +0 -291
  83. package/dist/__tests__/TestAvoStreamId.d.ts +0 -1
  84. package/dist/__tests__/TestAvoStreamId.js +0 -55
  85. package/dist/__tests__/TestDeduplicator.d.ts +0 -1
  86. package/dist/__tests__/TestDeduplicator.js +0 -102
  87. package/dist/__tests__/TestEventValidator.d.ts +0 -1
  88. package/dist/__tests__/TestEventValidator.js +0 -196
  89. package/dist/__tests__/TestNetworkCallsHandler.d.ts +0 -1
  90. package/dist/__tests__/TestNetworkCallsHandler.js +0 -86
  91. package/dist/__tests__/TestNetworkCallsHandlerPublic.d.ts +0 -1
  92. package/dist/__tests__/TestNetworkCallsHandlerPublic.js +0 -160
  93. package/dist/__tests__/TestParsing.d.ts +0 -1
  94. package/dist/__tests__/TestParsing.js +0 -128
  95. package/dist/__tests__/constants.d.ts +0 -42
  96. package/dist/__tests__/constants.js +0 -53
  97. package/examples/node-inspector-js/index.js +0 -30
  98. package/examples/node-inspector-js/package.json +0 -15
  99. package/examples/node-inspector-js/yarn.lock +0 -6
  100. package/examples/node-inspector-ts/dist/index.js +0 -34
  101. package/examples/node-inspector-ts/index.ts +0 -30
  102. package/examples/node-inspector-ts/package.json +0 -18
  103. package/examples/node-inspector-ts/tsconfig.json +0 -15
  104. package/examples/node-inspector-ts/yarn.lock +0 -11
  105. package/reason/AvoInspector.rei +0 -11
  106. package/reason/NodeAvoInspector.re +0 -27
  107. package/src/AvoDeduplicator.ts +0 -150
  108. package/src/AvoEncryption.ts +0 -90
  109. package/src/AvoGuid.ts +0 -9
  110. package/src/AvoInspector.ts +0 -498
  111. package/src/AvoInspectorEnv.ts +0 -8
  112. package/src/AvoNetworkCallsHandler.ts +0 -340
  113. package/src/AvoSchemaParser.ts +0 -115
  114. package/src/AvoStreamId.ts +0 -16
  115. package/src/__tests__/TestAvoEncryption.ts +0 -515
  116. package/src/__tests__/TestAvoEventSpecCache.ts +0 -242
  117. package/src/__tests__/TestAvoEventSpecFetcher.ts +0 -279
  118. package/src/__tests__/TestAvoInspector.ts +0 -376
  119. package/src/__tests__/TestAvoStreamId.ts +0 -72
  120. package/src/__tests__/TestDeduplicator.ts +0 -191
  121. package/src/__tests__/TestEventValidator.ts +0 -231
  122. package/src/__tests__/TestNetworkCallsHandler.ts +0 -181
  123. package/src/__tests__/TestNetworkCallsHandlerPublic.ts +0 -208
  124. package/src/__tests__/TestParsing.ts +0 -153
  125. package/src/__tests__/constants.ts +0 -63
  126. package/src/eventSpec/AvoEventSpecCache.ts +0 -119
  127. package/src/eventSpec/AvoEventSpecFetchTypes.ts +0 -47
  128. package/src/eventSpec/AvoEventSpecFetcher.ts +0 -186
  129. package/src/eventSpec/EventValidator.ts +0 -74
  130. package/src/index.ts +0 -2
  131. package/src/utils.ts +0 -49
  132. package/tsconfig.json +0 -13
package/CHANGELOG.md ADDED
@@ -0,0 +1,46 @@
1
+ # Changelog
2
+
3
+ ## 2.0.0
4
+
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
+
7
+ Breaking: the track call.
8
+
9
+ - **`trackSchemaFromEvent` takes one `InspectorEvent` object**, `trackSchemaFromEvent({ eventName, eventProperties, streamId?, outputReference?, originHint?, originAppVersion? })`, and it is the only form: the 1.x positional `trackSchemaFromEvent(eventName, eventProperties, streamId?)` is removed. `InspectorEvent` is exported as a TypeScript type. The wire output for the same values is unchanged. A call whose argument is not an object (such as an unchanged 1.x call from JavaScript) sends nothing, resolves `[]`, and prints an always-on, rate-limited error on stderr naming the fix. `eventProperties` may be absent or `null` (an event with no properties). Avo Codegen's internal entry point keeps its call shape.
10
+ - **Unknown `InspectorEvent` keys are warned about.** A key other than the six above (a typo such as `eventname` or `outputRef`) prints a warning on stderr naming the key, whatever the logging flag, at most once per 10 seconds. Only key names are printed, never values; the event is still tracked with the known keys.
11
+
12
+ Event spec validation (dev and staging):
13
+
14
+ - **Property values are checked against every value constraint in the event spec.** 1.x checked only the property type and the first regex pattern. Values are now checked against pinned values, allowed values, regex patterns and min/max ranges, on each property and on the properties of an object or of each object in a list (one level deep), with the same rules as the web and Android SDKs. A value outside a property's allowed values now shows up as a value issue in the Inspector Debugger.
15
+ - **Every event and variant in the spec is checked.** 1.x used only the first event the endpoint returned and ignored variants. Each constraint applies to the event and variant IDs the spec gives it, so a value can fail for one variant and pass for another.
16
+ - **Failures are reported against tracking-plan event IDs.** 1.x reported a failure against the Codegen event id, or the event name for a manual track, which the Debugger cannot match to an event. The wire now carries the failed event and variant IDs from the spec, and object properties' failures in their `children`. `passedEventIds` is no longer sent.
17
+ - **The property type is no longer compared with the spec.** Avo already compares the reported schema with the tracking plan, and the spec's type names differ from the schema's (`bool` and `boolean`, a list's element type and `list(…)`), so 1.x reported false failures for booleans, lists and whole numbers sent for `float` properties. A value of the wrong type still fails a constraint it cannot meet: a regex needs a string, a range a number.
18
+ - **Validation is bounded.** It runs synchronously in the track call, so a list of more than 1,000 items is not checked, and a string longer than 10,000 characters is not checked against regex patterns (each logged when logging is on). The event is still sent.
19
+ - **Gateway events are validated against the spec at their checkpoint.** An event tracked with `originHint` and/or `outputReference` now passes them to the event spec fetch (the same trimmed values as on the track body; a blank one is not sent), so a gateway key answers the spec of the gateway-input checkpoint (`originHint`) or of that output (`originHint` and `outputReference`). Each checkpoint's spec is cached and fetched on its own. A fetch without either is unchanged, and source keys ignore them. A gateway key answers 404 without `originHint`, and the event is then sent without validation, as for any spec it cannot fetch. A failed spec fetch is now cached, so it is not repeated for every event: a 404 (no spec, such as a gateway key without `originHint`) for the spec cache's 60 seconds, and any other failure (timeout, other status, network error, unparseable body) for 5 seconds, so a blip skips validation only briefly. Unlike a fetched spec, a cached failure is not refetched after 50 uses. ([AVO-4070](https://linear.app/avo/issue/AVO-4070))
20
+
21
+ Input handling. All of these behaved the same way in 1.x:
22
+
23
+ - **Null-prototype objects no longer wipe the schema.** A null-prototype object anywhere in the event properties (for example `querystring.parse()` output), or an object with its own `hasOwnProperty` key, made schema extraction fail: the event was sent with an empty schema and a stack trace was printed. These objects are now extracted exactly like plain objects, and deduplication, event spec validation and encryption handle them too.
24
+ - **A non-string `streamId` no longer rejects.** A number, bigint or boolean (a numeric user id, say) is sent as its string form. Any other non-string is treated as absent, with a warning when logging is on. Before, the track promise rejected, and a call without `.catch()` crashed the process with an unhandled rejection.
25
+ - **A non-string `env` falls back to `dev`** with a warning instead of throwing `value.trim is not a function`.
26
+ - **Non-whole numbers in exponent form are `float`.** `1e-7` and `5e-324` were classified `int`. A number is now `int` when it is a whole number and `float` otherwise; `NaN` and `±Infinity` are `float`.
27
+ - **Float typed arrays are `list(float)`.** A `Float32Array` or `Float64Array` is reported as `list(float)` with children `["float"]`, as the Java and Go SDKs type `float[]`/`double[]` and `[]float32`/`[]float64`, so the same data has the same type as a plain `[0.5, 1.5]`. Every other typed array, `Buffer` and `DataView` stays `list(int)` with `["int"]`. In 1.x a typed array was walked index by index and reported as an object with one property per element.
28
+ - **Equal list elements are reported once.** A list's children hold each distinct element schema once, in first-occurrence order, compared by value: objects with the same property names and types are equal whatever their property order, and nested lists compare their children in order. 1.x collapsed equal type strings but kept one child per object or nested list element, so a list of 1,000 similar objects carried 1,000 identical children. This matches the Java and Go SDKs.
29
+ - **A `null` list element is typed `"null"`.** `{ v: [null, 1] }` now has children `["null", "int"]`, following the spec's schema pseudocode; 1.x reported the null element as `[]`.
30
+ - **Invalid constructor arguments throw the documented messages.** A non-string `apiKey`, missing options (`new AvoInspector()`) or `null` options throw "[Avo Inspector] No API key provided…", and a non-string `version` throws "[Avo Inspector] No version provided…", instead of a `TypeError`.
31
+ - **Logs never contain property values.** With logging on, 1.x printed each event's raw properties ("Supplied event … with params …"), and because the logging flag is shared by every instance, a `dev` instance could make a `prod` one print user data such as emails. Log lines now show event names and the extracted schema (property names and types), never property values or the API key; event and property names are still printed, so they should not contain personal data.
32
+ - **Lost data is always logged.** Events dropped because the buffer or send backlog is full, because a send failed with an internal error, or because the process exited before they were sent (or before their send completed), batches rejected with a non-200 status (1.x did not log these at all), send failures and internal errors are printed on stderr whatever the logging flag, at most one line per kind per 10 seconds; the rest are counted and reported later (with the next occurrence, by `flush()` after the window, or at once by `destroy()` and at exit), worded by the real time they cover. Sampling drops and debug lines still need logging on.
33
+ - **An event without a name is sent as "Missing Event Name".** A `null`, `undefined`, non-string, empty or whitespace-only event name was sent as is (or with no `eventName` key at all). Such an event is now sent under the name `"Missing Event Name"`, with its full schema, in every env and without throwing, and an always-on, rate-limited line reports it. A valid name is sent unchanged, surrounding whitespace included.
34
+ - `package.json` declares `"types"` and `"engines": { "node": ">=14" }`.
35
+
36
+ Robustness under load:
37
+
38
+ - **At most 4 batches are sent at once.** Further batches wait their turn, and up to 10,000 events can wait; beyond that the oldest waiting events are dropped, and the drop is logged, so a fast producer or a slow or unresponsive endpoint cannot open thousands of requests, and the number of events held for sending stays bounded. `maxQueueSize` bounds only the unsent buffer. `flush()` waits for waiting batches, and `destroy()` discards them. An awaited track waits for a send slot once 1,000 events are waiting (backpressure, at most 10 s per call), which slows an awaited loop, such as an unchanged 1.x backfill script, down to the speed of the sends. Backpressure slows the backlog's growth but does not guarantee delivery: if the Inspector API is slow or down for long, the backlog can still fill and the oldest waiting events are dropped (logged). Calls that are not awaited are not slowed down (see the README).
39
+ - **The exit drain is bounded.** In-flight requests do not hold the process open. On `beforeExit` the SDK attempts to send everything left under a single 10-second deadline, and holds the process no longer than that for its own drain (plus up to 1 s for events still waiting for their event spec, and up to 10 s for un-awaited tracks still waiting on a full send backlog); other work in your process can still keep it running longer. Events still waiting for their spec halfway through are sent without validation, and whatever could not be sent is reported at exit. An awaited track call keeps the process alive until it settles (at most 30 s per call, before any exit drain), so an awaited loop waiting on spec fetches or dev sends is not taken for the exit; a call still pending after 30 s (such as a batch-size-1 send still waiting for a send slot) resolves with its schema so the script carries on, and its event stays queued for the exit drain or is reported as unsent at exit; this applies to any call whose promise is observed (`await`, `.then`, `.catch`, `.finally`, `Promise.all`), so a fire-and-forget `track(...).catch(() => {})` in dev or staging holds the process within the same bounds. Only a call whose promise nothing touches holds nothing.
40
+ - **Schema extraction is bounded.** It stops expanding a value more than 10 levels deep, a value that contains itself, and anything past 10,000 objects and lists in one call; such a value is reported as `"object"`. At most 10,000 properties are reported per call, and binary data (a `Buffer`, typed array or `ArrayBuffer`) is reported by type without visiting its bytes: a 1 MiB Buffer used to produce a million-property schema, and larger ones crashed the process. In 1.x a cyclic or very deep payload made extraction fail and the event was sent with an empty schema. See "Schema extraction limits" in the README.
41
+ - **A response cut off mid-body is judged by its status.** A 200 whose body ends early (the connection closes before the full `Content-Length`) counts as delivered, like a 200 with an unparseable body: the sampling rate is unchanged and no failed send is logged. A cut-off non-200 counts as that non-200. This matches the Java and Go SDKs.
42
+ - **Lower CPU cost per event.** The Codegen/manual deduplicator's cleanup no longer scans every recent timestamp on each call, which took most of the CPU time under load. As a side effect, a registration that shared its millisecond with another now expires after 500 ms like any other.
43
+ - **`flush()` reports whether the instance drained.** `flush(timeoutMs?)` resolves `true` when the instance has nothing buffered, waiting or in flight, and `false` when the timeout passed first. It still never rejects, and resolves `true` after `destroy()`. A backfill script can flush until drained, with a bounded number of attempts (see the README).
44
+ - **`flush()` during event spec validation sends batches.** Events whose validations finish together are sent as one batch, not one request per event.
45
+ - **One shared connection pool for event spec fetches.** Every instance reuses one keep-alive agent (at most 8 sockets, 2 kept idle), so creating instances without destroying them no longer adds a connection pool, and its idle connections, per instance.
46
+ - **Event spec fetches queued for a connection keep their full 10 s budget, and at most 1,000 events wait for a spec at once.** The 10 s deadline starts when a fetch gets a connection, not when it is requested, so fetches queued behind slow ones are no longer timed out unsent; a fetch that gets no connection within 10 s (for example behind 8 hung ones) is abandoned and its event sent without validation. Past 1,000 waiting events (across every instance), further events are sent without validation instead of waiting.
package/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  This is a quick start guide. For more information about the Inspector project please read the [Inspector SDK Reference](https://www.avo.app/docs/implementation/avo-inspector-sdk-reference) and the [Inspector Setup Guide](https://www.avo.app/docs/implementation/setup-inspector-sdk).
6
6
 
7
+ Implements [avohq/spec-first-inspector-server-sdk](https://github.com/avohq/spec-first-inspector-server-sdk) v3.0.1 (exported as `SPEC_VERSION`).
8
+
7
9
  # Installation
8
10
 
9
11
  The library is distributed with npm, install with npm:
@@ -31,12 +33,111 @@ let inspector = new Inspector.AvoInspector({
31
33
  });
32
34
  ```
33
35
 
36
+ Then report each event where you track it, as one `InspectorEvent` object:
37
+
38
+ ```javascript
39
+ inspector.trackSchemaFromEvent({ eventName: "Purchase", eventProperties: { amount: 42 } });
40
+ ```
41
+
42
+ With a gateway-scoped API key, always pass `originHint` and `originAppVersion`. Pass `outputReference` when the payload was bound for a specific output; leave it out for an observation at the gateway checkpoint (see [Gateway options](#gateway-options)):
43
+
44
+ ```javascript
45
+ inspector.trackSchemaFromEvent({
46
+ eventName: "Purchase",
47
+ eventProperties: { amount: 42 },
48
+ outputReference: "meta-x7k2q",
49
+ originHint: "android",
50
+ originAppVersion: "4.2.0",
51
+ });
52
+ ```
53
+
54
+ ## Batching options
55
+
56
+ Events are buffered in memory and sent in batches. All options are optional:
57
+
58
+ | Option | Default | Meaning |
59
+ |---|---|---|
60
+ | `batchSize` | `30` | Send when this many events are buffered. Always `1` in `dev`, so every event is sent on its own as soon as it is queued. |
61
+ | `batchFlushSeconds` | `30` | Send once the oldest buffered event is this many seconds old. |
62
+ | `maxQueueSize` | `1000` | Maximum events buffered before a batch is formed; the oldest are dropped first when it is exceeded, and the drop is logged. Batches already formed and waiting to be sent have their own limit (see [High-volume and backfill scripts](#high-volume-and-backfill-scripts)). |
63
+ | `disableBatchTimer` | `false` | Start no background flush timer. Set it to `true` in serverless functions. |
64
+
65
+ ```javascript
66
+ let inspector = new Inspector.AvoInspector({
67
+ apiKey: "your api key",
68
+ env: Inspector.AvoInspectorEnv.Prod,
69
+ version: "1.0.0",
70
+ appName: "My app",
71
+ batchSize: 50,
72
+ batchFlushSeconds: 10,
73
+ });
74
+ ```
75
+
76
+ While an instance has events that are buffered or still being sent, the SDK keeps a reference to it so they can be sent when the process ends. Such an instance is not garbage-collected until its events are sent or it is destroyed, even if your code has dropped it. With `disableBatchTimer: true` and no `flush()`, buffered events leave only on a size trigger or at exit, so the instance and its buffer stay in memory until then. Call `flush()` or `destroy()` when you are done with an instance.
77
+
78
+ ### High-volume and backfill scripts
79
+
80
+ At most 4 requests are sent at once. Batches formed while all 4 are busy wait their turn, and up to 10,000 events can wait. Beyond that the oldest waiting events are dropped and the drop is logged, so the number of events held for sending stays bounded when the endpoint is slow or down.
81
+
82
+ A loop that awaits each track, such as a backfill script doing `for (...) await inspector.trackSchemaFromEvent(...)`, is slowed down to the speed of the sends, as in 1.x: once 1,000 events are waiting for a send slot, the promise of the next track call resolves only when fewer than 1,000 wait (the event itself is already queued). Each such call waits at most about one request timeout (10 seconds), so against an endpoint that is slower than that for long, the backlog can still fill and the oldest waiting events are dropped (the drop is logged): backpressure slows the backlog's growth but doesn't guarantee delivery. Calls you don't await are not slowed down, so a loop that tracks without awaiting can still fill the 10,000-event backlog and drop the oldest waiting events.
83
+
84
+ At the end, flush until the instance reports drained. Each `flush()` waits at most `timeoutMs` (10 seconds by default) and resolves `true` once nothing is buffered, waiting or in flight, or `false` if the timeout passed first. Bound the attempts: each one shrinks the backlog, but against a hung endpoint draining can take minutes (up to about 1,000 waiting events, in batches of 30 sent 4 at a time, each giving up after 10 seconds), and tracking from elsewhere in the process can keep it from ever reporting drained.
85
+
86
+ ```javascript
87
+ for (const row of rows) {
88
+ await inspector.trackSchemaFromEvent({ eventName: row.event, eventProperties: row.properties });
89
+ }
90
+
91
+ let drained = false;
92
+ for (let attempt = 0; attempt < 6 && !drained; attempt++) {
93
+ drained = await inspector.flush();
94
+ }
95
+ if (!drained) {
96
+ console.error("Backfill: Avo Inspector did not drain; some events may not have been sent.");
97
+ }
98
+ ```
99
+
100
+ # Flushing before exit (required)
101
+
102
+ Buffered events live in memory only and are lost if the process exits first. Delivery is at-most-once: a batch that fails to send is dropped, never retried.
103
+
104
+ When a process ends because it has nothing left to do, the SDK sends what is still buffered on its own: it listens for Node's `beforeExit` event and flushes, holding the process for at most 10 seconds for this drain (or until the deadline of a `flush(timeoutMs)` you started that is still running, if later). The deadline bounds the SDK's own drain, not the process: other work in your process can keep it running longer. While you await a `trackSchemaFromEvent` call that is waiting on its event spec (in `dev` and `staging`) or, in `dev`, on its send, the SDK keeps the process alive until that call settles, so an awaited loop is not mistaken for the end of the process. That wait is at most 30 seconds per call (a spec fetch can take up to 20 seconds when it first waits for a connection, then a `dev` send up to 10). A call still pending after 30 seconds, for example a send with batch size 1 still waiting for one of the 4 send slots, resolves with its schema so your script carries on; its event stays queued and goes out with the exit drain, or is reported as unsent at exit. If your script then ends, the exit drain described here can add up to 10 seconds more, so a script ending on such a call against unresponsive endpoints can take about 40 seconds to exit. This applies to any call whose promise is observed (`await`, `.then`, `.catch`, `.finally`, `Promise.all`, ...), so a fire-and-forget `inspector.trackSchemaFromEvent(...).catch(() => {})` in `dev` or `staging` holds the process within the same bounds. Only a call whose promise nothing touches holds nothing. Two short waits can come before the drain: if tracks you didn't await are still waiting on a full send backlog (see [High-volume and backfill scripts](#high-volume-and-backfill-scripts)), the drain starts once they are released, within 10 seconds; and if events are still waiting for their event spec, the SDK first gives them up to 1 second. Events whose spec still hasn't arrived halfway through the drain are sent without validation. Events that could not be sent in time are reported on stderr when the process exits. This is a best-effort safety net, not a guarantee. The SDK never keeps an idle process alive, and `beforeExit` does **not** fire when:
105
+
106
+ - the process calls `process.exit()`;
107
+ - the process is stopped by a signal such as `SIGTERM` or `SIGINT` (container shutdown, Ctrl-C);
108
+ - a serverless platform freezes or reclaims the function after the handler returns.
109
+
110
+ So call `flush()` yourself in those cases:
111
+
112
+ - Call `await inspector.flush()` before `process.exit()`, in your `SIGTERM`/`SIGINT` handlers, and before a serverless handler (AWS Lambda, Google Cloud Functions, Vercel, ...) returns. It sends everything buffered, waits for in-flight requests (up to `timeoutMs`, default 10000) and never rejects. It resolves once those send attempts finish, whether or not they succeeded: a batch that fails is dropped, not retried. It resolves `true` if the instance then has nothing buffered, waiting or in flight, and `false` if the timeout passed first (`flush(0)` starts the sends and reports whether anything is still pending). After `destroy()` it resolves `true`.
113
+ - In serverless functions, also pass `disableBatchTimer: true`.
114
+
115
+ ```javascript
116
+ export const handler = async (event) => {
117
+ await inspector.trackSchemaFromEvent({ eventName: "Order Placed", eventProperties: { amount: 42 } });
118
+ await inspector.flush();
119
+ };
120
+
121
+ process.on("SIGTERM", async () => {
122
+ await inspector.flush();
123
+ process.exit(0);
124
+ });
125
+ ```
126
+
127
+ `destroy()` is different: it discards buffered events without sending them, abandons in-flight requests and stops the timer. After `destroy()`, `trackSchemaFromEvent` resolves `[]` and sends nothing; a call still waiting on its send or on an event spec fetch when `destroy()` runs also resolves `[]`.
128
+
34
129
  # Integrating with Avo Codegen
35
130
 
36
131
  The setup is lightweight and is covered [in this guide](https://www.avo.app/docs/implementation/start-using-inspector-with-avo-functions).
37
132
 
38
133
  Every event sent with your Codegen after this integration will automatically be sent to Inspector.
39
134
 
135
+ ## Deduplication with Codegen
136
+
137
+ If you also call `trackSchemaFromEvent` for events that Codegen already reports, the SDK drops the second report of the same observation. A Codegen call and a manual call are treated as duplicates when they have the same event name, the same stream id and deeply equal properties, and arrive within 500 ms of each other in either order. The duplicate call sends nothing and resolves `[]`. Two manual calls, or two Codegen calls, are never deduplicated against each other. The comparison stops at the [schema extraction limits](#schema-extraction-limits): properties nested more than 10 levels deep, with more than 10,000 objects and lists, or with more than 10,000 properties to compare (counting nested ones), are treated as different, so both calls are sent. The exception is a Codegen call and a manual call that pass the very same properties object: the same reference always compares equal, before any limit applies.
138
+
139
+ Calls that carry [gateway fields](#gateway-options) are never deduplicated: each gateway output is a distinct observation. They still go through sampling and batching like any other event.
140
+
40
141
  # Sending event schemas for events reported outside of Codegen
41
142
 
42
143
  Whenever you send tracking event call the following methods:
@@ -47,13 +148,101 @@ This method gets actual tracking event parameters, extracts schema automatically
47
148
  It is the easiest way to use the library, just call this method at the same place you call your analytics tools' track methods with the same parameters.
48
149
 
49
150
  ```javascript
50
- inspector.trackSchemaFromEvent("Event name", {
51
- "String Prop": "Prop Value",
52
- "Float Prop": 1.0,
53
- "Boolean Prop": true,
151
+ inspector.trackSchemaFromEvent({
152
+ eventName: "Event name",
153
+ eventProperties: {
154
+ "String Prop": "Prop Value",
155
+ "Float Prop": 1.0,
156
+ "Boolean Prop": true,
157
+ },
54
158
  });
55
159
  ```
56
160
 
161
+ `trackSchemaFromEvent` takes one `InspectorEvent` object (exported as a TypeScript type):
162
+
163
+ | Field | Meaning |
164
+ |---|---|
165
+ | `eventName` | The event name. A missing, empty or whitespace-only name is sent as `"Missing Event Name"`. |
166
+ | `eventProperties` | The event's properties. Absent or `null`: an event with no properties. |
167
+ | `streamId` | Optional stream id to correlate events. |
168
+ | `outputReference`, `originHint`, `originAppVersion` | Gateway fields, see [Gateway options](#gateway-options). |
169
+
170
+ Any other key (a typo such as `eventname` or `outputRef`) is ignored and prints a warning on stderr naming the key, whatever the logging flag, at most once per 10 seconds: `[Avo Inspector] Warning: unknown InspectorEvent key(s) "outputRef" ignored; the known keys are eventName, eventProperties, streamId, outputReference, originHint, originAppVersion`. Only key names are printed, never values. The event is still sent with the keys that were recognised.
171
+
172
+ `trackSchemaFromEvent` returns a promise that resolves with the extracted schema once the event has been through sampling: when the event is queued, or at once when sampling drops it (a dropped event is never queued, so `flush()` does not send it either). In `dev` a queued event is sent within the call, and the promise resolves `[]` if the Inspector API answers with a non-200 status. Only a non-200 does: if the request fails (connection refused, timeout) the promise still resolves the schema, and the failure is logged on stderr, so don't treat a resolved schema as proof of delivery. When 1,000 events are already waiting for a send slot, the promise resolves once fewer wait (see [High-volume and backfill scripts](#high-volume-and-backfill-scripts)).
173
+
174
+ ## Schema extraction limits
175
+
176
+ Schema extraction runs on your thread, inside `trackSchemaFromEvent`. Without limits, a payload such as an ORM object or a full API response could make that work unbounded, so extraction stops expanding a value:
177
+
178
+ - more than 10 levels deep, where each step into an object or into a list element counts as one level;
179
+ - that contains itself (a cycle);
180
+ - once 10,000 objects and lists have been expanded in one call (the event properties object, every list and every piece of binary data count).
181
+
182
+ A property cut off this way is reported as `"object"` with empty `children`; a list element cut off this way is reported as the type string `"object"`. Strings, numbers and booleans never count toward the limits, whatever their size.
183
+
184
+ At most 10,000 properties are reported per call, counting nested ones; past that the remaining properties are left out, in the order the object lists them.
185
+
186
+ A list's `children` hold each distinct element schema once, in the order they first appear: `[{ a: 1, b: "x" }, { b: "y", a: 2 }, { a: 3 }]` has two children, `[{a: int, b: string}]` and `[{a: int}]`, because objects with the same property names and types are equal whatever their property order. Every element still counts toward the limits above.
187
+
188
+ Binary data is never walked byte by byte: a `Float32Array` or `Float64Array` is reported as `list(float)` with children `["float"]`, any other typed array, `Buffer` or `DataView` as `list(int)` with children `["int"]`, and an `ArrayBuffer` as `"object"` with empty `children`, whatever their size. Each one still counts as one value toward the limits above, like an object or a list: past them it is cut off and reported as `"object"`, as the Java and Go SDKs do.
189
+
190
+ For example, an object that refers to itself:
191
+
192
+ ```javascript
193
+ const order = { id: 7 };
194
+ order.self = order;
195
+ inspector.extractSchema({ order });
196
+ // [{ propertyName: "order", propertyType: "object", children: [
197
+ // { propertyName: "id", propertyType: "int" },
198
+ // { propertyName: "self", propertyType: "object", children: [] } ] }]
199
+ ```
200
+
201
+ ## Event order
202
+
203
+ Each event carries its own `createdAt`, stamped when `trackSchemaFromEvent` is called. Events in a batch are not guaranteed to be in call order: in `dev` and `staging`, an event whose spec must first be fetched for validation joins the queue when the fetch completes, so it can be sent after events tracked later. Use `createdAt` if you need the call order.
204
+
205
+ At most 1,000 events wait for an event spec fetch at once, across every instance in the process. When that many are already waiting (for example when the Avo API is slow), further events are sent at once without validation instead of waiting. A spec fetch that gets no connection within 10 seconds, or no answer within 10 seconds of getting one, is abandoned, and its event is sent without validation, so an event waits for its spec for about 20 seconds at most.
206
+
207
+ ## Gateway options
208
+
209
+ When you use a gateway-scoped Inspector API key, pass the gateway coordinates as fields of the `InspectorEvent`:
210
+
211
+ ```javascript
212
+ inspector.trackSchemaFromEvent({
213
+ eventName: "Purchase",
214
+ eventProperties: { amount: 42 },
215
+ outputReference: "meta-x7k2q", // the gateway output this event was bound for; omit for the gateway checkpoint
216
+ originHint: "android", // which source the event came from
217
+ originAppVersion: "4.2.0", // that source's app version
218
+ });
219
+ ```
220
+
221
+ - `originHint` must be a low-cardinality label such as `"web"`, `"ios"` or `"android"`, never a user id or any other high-cardinality value.
222
+ - Values are trimmed; blank or non-string values are ignored.
223
+ - A misspelt field (such as `outputRef`) is ignored with a warning, like any unknown `InspectorEvent` key (see [Sending event schemas](#sending-event-schemas-for-events-reported-outside-of-codegen)).
224
+ - `originAppVersion` replaces the constructor `version` for that event. If you pass `originHint` without `originAppVersion`, the event is sent without an app version (`null`), because the constructor version belongs to a different source.
225
+
226
+ # Property value validation
227
+
228
+ In `dev` and `staging`, the SDK fetches each event's spec from your tracking plan and checks property values against the constraints set there: pinned values, allowed values, regex patterns and min/max ranges. It checks each property and the properties of an object, or of each object in a list, one level deep. The results go out with the event, and value issues show up in the [Inspector Debugger](https://www.avo.app/docs/inspector/inspector-debugger). In `prod` no spec is fetched and only the schema is sent.
229
+
230
+ To use it, enable property value validation in your Avo workspace's Inspector settings. To see the values themselves in the Debugger, also pass a `publicEncryptionKey` (a hex P-256 public key): values are encrypted with it before they leave your process, and only you can decrypt them, with your private key. See [Generating encryption keys](https://www.avo.app/docs/inspector/inspector-debugger#generating-encryption-keys).
231
+
232
+ ```javascript
233
+ let inspector = new Inspector.AvoInspector({
234
+ apiKey: "your api key",
235
+ env: Inspector.AvoInspectorEnv.Dev,
236
+ version: "1.0.0",
237
+ appName: "My app",
238
+ publicEncryptionKey: "your hex public key",
239
+ });
240
+ ```
241
+
242
+ An event whose spec cannot be fetched (an error, a timeout, or an event that is not in the tracking plan) is sent without validation.
243
+
244
+ With a gateway-scoped API key, the spec fetch carries the event's `originHint` and `outputReference` (see [Gateway options](#gateway-options)), so values are checked against the event as expected at that checkpoint: `originHint` alone selects the gateway-input checkpoint, and both select that output. Without `originHint`, a gateway key has no spec for the event, which is then sent without validation. Each checkpoint's spec is cached separately. Source keys ignore these fields.
245
+
57
246
  # Enabling logs
58
247
 
59
248
  Logs are enabled by default in the dev mode and disabled in prod mode. You can enable and disable logs by calling the `enableLogging` method:
@@ -62,6 +251,83 @@ Logs are enabled by default in the dev mode and disabled in prod mode. You can e
62
251
  inspector.enableLogging(true | false);
63
252
  ```
64
253
 
254
+ The logging flag is shared by every instance in the process, and each constructor resets it, so creating a `dev` instance turns logging on for a `prod` instance too. Log lines never contain property values or the API key. They do show event names and the extracted schema (property names and types), so do not put personal data such as emails in event or property names.
255
+
256
+ Some lines are printed whatever the logging flag, on stderr, because they report lost data or failed sends:
257
+
258
+ - `Avo Inspector: dropped N event(s) (queue full) in the last Ns.` or `(send backlog full)`: events dropped because the buffer (`maxQueueSize`) or the 10,000-event send backlog is full; `(internal error)`: events in a batch whose send failed with an internal error (logged with the internal-error line below); `(unsent at exit)`: events never sent when the process exited (naturally, past the 10-second exit drain, or through `process.exit()`): still buffered, waiting for a send slot, or still being validated; `(unconfirmed at exit)`: events in sends that had not completed when the process exited, which may or may not have arrived. A process stopped by a signal without a handler prints nothing, so call `flush()` in your signal handlers;
259
+ - `[Avo Inspector] Error: since 2.0.0, trackSchemaFromEvent takes one InspectorEvent object; nothing was sent. Replace trackSchemaFromEvent(eventName, eventProperties) with trackSchemaFromEvent({ eventName, eventProperties }).`: a track call whose argument is not an object, such as a 1.x call with the event name first. Nothing is sent and the call resolves `[]`;
260
+ - `[Avo Inspector] Warning: unknown InspectorEvent key(s) "…" ignored; …`: see [Sending event schemas](#sending-event-schemas-for-events-reported-outside-of-codegen);
261
+ - `Avo Inspector: N event(s) tracked without an event name in the last Ns, sent as "Missing Event Name".`: track calls whose event name is `null`, `undefined`, not a string, empty or whitespace-only. The event is still sent, under the name `"Missing Event Name"`, and the call resolves its schema as usual;
262
+ - `Avo Inspector: N batch(es) rejected with HTTP <status> in the last Ns.`: the Inspector API answered with a status other than 200 (only the status is printed);
263
+ - `Avo Inspector: schema sending failed: Request failed.` or `Request timed out.`: a batch could not be sent;
264
+ - `Avo Inspector: something went wrong. Please report to support@avo.app. (<error type>)`: an internal error. Only the error's type (for example `TypeError`) is printed, never its message, which could contain a property value.
265
+
266
+ Each kind prints at most one line per 10 seconds (per reason or status): the first occurrence prints at once, and later ones are counted, then reported with the next occurrence after the 10 seconds, by `flush()` once the 10 seconds have passed, or at once by `destroy()` and when the process exits (naturally or through `process.exit()`). So a burst is always reported, even if it never recurs. Idle moments of a script whose only pending work is the SDK's, such as the pause after each awaited `flush()` in a loop, are not exits: they print only counts whose 10 seconds have passed. "in the last Ns" is the real time the count covers, in whole seconds (for example `(12 more in the last 37s)`); a line about a single occurrence says `1s`. Sends abandoned by `destroy()` and events dropped by sampling are not reported.
267
+
268
+ # Upgrading from 1.x to 2.0
269
+
270
+ 2.0 implements spec 3.0.1. These are the changes you may notice:
271
+
272
+ - **`trackSchemaFromEvent` takes one `InspectorEvent` object.** The positional forms are gone: the event name, properties and stream id are fields of the object, alongside the gateway fields. Every call site changes:
273
+
274
+ ```javascript
275
+ // 1.x
276
+ inspector.trackSchemaFromEvent("Purchase", { amount: 42 }, "user-123");
277
+
278
+ // 2.0
279
+ inspector.trackSchemaFromEvent({ eventName: "Purchase", eventProperties: { amount: 42 }, streamId: "user-123" });
280
+ ```
281
+
282
+ TypeScript flags a 1.x call at compile time. In JavaScript, a call whose argument is not an object sends nothing, resolves `[]` and prints `[Avo Inspector] Error: since 2.0.0, trackSchemaFromEvent takes one InspectorEvent object; nothing was sent. …` on stderr, whatever the logging flag (at most once per 10 seconds). Avo Codegen's generated calls are unchanged.
283
+ - **Property values are checked against every value constraint of the event spec, not the type** (see [Property value validation](#property-value-validation)). 1.x compared the property type and one regex. Failures are now reported against the tracking plan's event and variant IDs.
284
+ - **The promise resolves when the event is queued (or dropped by sampling), not when it is delivered.** Outside `dev`, events are batched (see [Batching options](#batching-options)), so `await inspector.trackSchemaFromEvent(...)` no longer means the event reached Avo. In `dev` each event is still sent within the call.
285
+ - **The SDK no longer keeps your process alive.** 1.x ran a keep-alive timer while sends were pending; it is gone. Buffered events are still sent, best-effort, when the process ends naturally (on `beforeExit`), but not on `process.exit()`, on signals, or when a serverless function is frozen. Call `await inspector.flush()` in those cases (see [Flushing before exit](#flushing-before-exit-required)).
286
+ - **A non-200 response in `dev` resolves `[]`.** 1.x resolved the extracted schema whatever the status. Outside `dev` the promise resolves the schema, because the send happens later.
287
+ - **`destroy()` terminates the instance.** It discards buffered events unsent and aborts in-flight requests. Afterwards `trackSchemaFromEvent` resolves `[]` and sends nothing; 1.x kept sending.
288
+ - **`callInspectorWithBatchBody` changed**, if you call it directly:
289
+ - it no longer applies sampling (sampling now happens per event when it is queued);
290
+ - it resolves with the HTTP status code instead of `undefined`;
291
+ - besides network errors and timeouts, it rejects with `"Request failed"` without sending when a header value contains a control character (anything but tab) or a character above U+00FF.
292
+ - **An API key containing a control character (anything but tab) or a character above U+00FF now throws in the constructor**, because the key is sent as a request header.
293
+ - **At most 4 batches are sent at once**, and up to 10,000 events can wait to be sent; beyond that the oldest waiting events are dropped. An awaited `trackSchemaFromEvent` waits for a send slot once 1,000 events are waiting (at most 10 seconds per call), which slows an awaited loop down to the speed of the sends, as in 1.x. This slows the backlog's growth but doesn't guarantee delivery: if the Inspector API is slow or down for long, the oldest waiting events can still be dropped (logged). Calls that are not awaited are not slowed down (see [High-volume and backfill scripts](#high-volume-and-backfill-scripts)).
294
+ - **A non-string `streamId` is converted, not rejected.** A number, bigint or boolean is sent as its string form; any other non-string is ignored. In 1.x the track promise rejected.
295
+ - **A non-string `env` falls back to `dev`** with a warning. In 1.x the constructor threw a `TypeError`.
296
+ - **`NaN`, `±Infinity` and exponent-form numbers such as `1e-7` are classified `float`.** 1.x classified them `int`.
297
+ - **A `null` list element is typed `"null"`.** `{ v: [null, 1] }` has children `["null", "int"]`; 1.x reported the null element as `[]`.
298
+ - **Node.js 14 or later is required** (`"engines": { "node": ">=14" }`).
299
+ - **Wire changes:** requests go to `https://api.avo.app/inspector/v2/track` and carry the API key and env as `api-key` and `env` headers. Bodies of 1024 bytes or more are gzipped. Events no longer carry `sessionId` or `trackingId`, and every event now carries `streamId`. `sessionId` is not sent; ingestion treats it as optional.
300
+
301
+ Unchanged from 1.x, but easy to trip over: the logging flag is shared by every instance in the process, and each constructor resets it (on for `dev`, off otherwise). Creating a `prod` instance after a `dev` one turns logging off for both. Call `enableLogging` after constructing your instances if you need a specific setting.
302
+
303
+ # Testing
304
+
305
+ To test against a local mock server, set the `AVO_INSPECTOR_MOCK_ENDPOINT` environment variable to its URL, for example `http://127.0.0.1:9876`. It exists for the spec's conformance suite and for local mock servers (spec §7.1).
306
+
307
+ - Track requests and event spec fetches go to that URL exactly as they would go to Avo, including your real API key (in the `api-key` header and the spec query), so point it only at a server you control.
308
+ - Don't set it in shared `staging` or `dev` environments (a CI image or base environment that many services inherit, for example): every non-prod instance there would send its API key and events to that host. Only `prod` instances ignore it.
309
+ - It is ignored when `env` is `prod`, whatever its value.
310
+ - Event spec fetches (`dev` and `staging`) go to the same server: `GET <scheme>://<host>:<port>/trackingPlan/eventSpec?apiKey=…&eventName=…&streamId=…`, with the API key in the query, followed by `&originHint=…` and `&outputReference=…` for an event that carries them. A mock that has no specs to serve can answer `200` with `{"events": [], "metadata": {}}` (events are then sent without validation); any other answer, such as a 404, also sends them without validation.
311
+ - The variable is read once, when an instance is created; set it before constructing your instances.
312
+ - The first redirected send prints a one-time warning naming the scheme, host and port (never the path, the query or the API key).
313
+ - A value that is not an `http` or `https` URL is ignored with a one-time warning that gives the reason but never the value, and requests go to Avo as usual.
314
+
315
+ # Development
316
+
317
+ ## Releasing
318
+
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
+
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
+
323
+ ## Conformance suite
324
+
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.
326
+
327
+ ```
328
+ ./scripts/run-conformance.sh
329
+ ```
330
+
65
331
  ## Author
66
332
  Avo (https://www.avo.app), hi@avo.app
67
333
 
@@ -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
+ }