@autter/runtime-node 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -48,6 +48,38 @@ expectOperation(runtime, "POST /checkout").toHaveOutcome("degraded").toHaveError
48
48
 
49
49
  Full guide: [Requests, coded errors and background work](../../docs/REQUESTS-AND-ERRORS.md).
50
50
 
51
+ # Breadcrumbs (1.6.0)
52
+
53
+ The steps before an error, scoped to the current request:
54
+
55
+ ```ts
56
+ import { addBreadcrumb, runtimeLogger } from "@autter/runtime-node";
57
+
58
+ app.post("/checkout", async (req, res) => {
59
+ addBreadcrumb({ message: "Loaded cart", data: { items: 3 } }); // type "custom"
60
+ runtimeLogger.info("Charging card", { provider: "stripe" }); // recorded as a "log" breadcrumb
61
+ await charge(); // a throw here carries both
62
+ });
63
+ ```
64
+
65
+ - Scope: inside `autterRequests()` / `withRuntimeRequest()` /
66
+ `withRuntimeOperation()` the trail belongs to the request and is shared with
67
+ its child operations and forks (AsyncLocalStorage). Inside a traced request
68
+ without the middleware it belongs to the trace; outside both, to the process
69
+ (so a crash outside any request carries what happened before it).
70
+ - The last 30 are kept. `captureException`, `captureMessage` and errors thrown
71
+ out of operations attach them to the exception span event as
72
+ `autter.breadcrumbs` (a JSON string, at most 16 KB, or less when
73
+ `OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT` is lower).
74
+ - Every `runtimeLogger` / `runtimeContext.debug|info|warn|error` entry is
75
+ recorded as a `log` breadcrumb with its level and up to 10 primitive
76
+ attributes.
77
+ - Messages (≤ 500 characters) and data (≤ 10 keys of strings, numbers,
78
+ booleans or null) are scrubbed with your `redact` settings.
79
+ - The browser relay forwards browser breadcrumbs, re-validated and re-scrubbed.
80
+ - Needs ingester 1.6.0. Logger-only mode (`initAutterLogging`) and
81
+ `@autter/runtime-edge` don't attach them yet.
82
+
51
83
  # @autter/runtime-node
52
84
 
53
85
  The server tracker exports per-instance RSS, heap, memory limit, and GC
@@ -113,6 +145,12 @@ const autter = initAutterServer({
113
145
  // handled errors — always recorded, never sampled out:
114
146
  autter.captureException(err, { "order.id": "…" });
115
147
 
148
+ // fingerprint override — every error carrying the same fingerprint is one
149
+ // issue, whatever its message or stack (a string or up to 10 strings, each
150
+ // ≤200 chars; sent as `autter.fingerprint` on the exception event; needs
151
+ // ingester 1.6.0+, older ingesters ignore it):
152
+ autter.captureException(err, { fingerprint: ["checkout", "card-timeout"] });
153
+
116
154
  // warnings/info without an exception — same grouping+aggregation as
117
155
  // errors, just a lower severity ("fatal" | "error" | "warning" | "info"):
118
156
  autter.captureMessage("Legacy /orders lookup used", "warning");
@@ -142,10 +180,11 @@ Defaults (cheap by construction):
142
180
  | Traces containing an error | 100% (tail retention, `retainTracesOnError`) |
143
181
  | `withProcessSpan` spans | 100% (same always-on tracer) |
144
182
  | LLM/GenAI call spans | 100% (`llmTracing`, on by default) |
145
- | Healthy traces | 1% head sampling (`traceSampleRate`) |
183
+ | Healthy traces | 1% head sampling (`traceSampleRate`; also for requests whose incoming `traceparent` is unsampled, such as browser fetches from `@autter/runtime-browser`) |
146
184
  | Request metrics | exported every 60 s |
147
185
  | Logs | not collected |
148
186
  | PII in custom attributes | redacted before export (`redactAttributes`) |
187
+ | Query strings in span URLs | parameter names only (`keepQueryParams`) |
149
188
  | Forgotten `shutdown()` | exporters still flushed on exit (`autoFlush`) |
150
189
 
151
190
  **Error-linked trace retention.** Errors export at 100% while traces are
@@ -167,6 +206,63 @@ import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
167
206
  initAutterServer({ ..., instrumentations: [new ExpressInstrumentation()] });
168
207
  ```
169
208
 
209
+ ### Server errors keep their stacks
210
+
211
+ When a handler throws, the framework answers 500 — and on its own that 500
212
+ is all the tracker sees: a stackless `HttpServerError` grouped by route. Add
213
+ your framework's hook and the thrown error is recorded, with its stack, on
214
+ the request span; the framework then carries on exactly as before.
215
+
216
+ ```ts
217
+ import { autterErrorHandler, autterFastifyErrors, autterKoa, captureRequestError } from "@autter/runtime-node";
218
+
219
+ // Express / Connect: after your routes, before your own error handlers.
220
+ // Records the error, then calls next(err) unchanged.
221
+ app.use(autterErrorHandler());
222
+
223
+ // Fastify 4/5: an onError hook (Fastify's error handler still answers).
224
+ // autterFastify (request summaries) already includes it.
225
+ app.register(autterFastifyErrors); // or autterFastifyErrors(app)
226
+
227
+ // Koa 2/3: mount first; records and rethrows, so app.on("error") still fires.
228
+ app.use(autterKoa());
229
+
230
+ // Anything else (NestJS exception filters, hapi onPreResponse, …):
231
+ captureRequestError(err, { "app.handler": "checkout" });
232
+ ```
233
+
234
+ All three report errors with status ≥ 500 (or none), RuntimeErrors and coded
235
+ errors — `ctx.throw(404)` and validation failures stay out of your issues.
236
+ Pass `{ capture: true }` to report every error, or a predicate. An error is
237
+ reported once, even if your handler already called `captureException`, and it
238
+ lands on the request's HTTP server span even when an instrumentation's child
239
+ span is active (`captureRequestError` still adds its attributes to that span
240
+ when the error was already reported). Errors from another JavaScript realm
241
+ (a `vm` context, Next's edge sandbox under `next start`) keep their own type,
242
+ message and stack. `autterErrorResponse()` reports the same way and also
243
+ answers the request.
244
+
245
+ ### Unhandled promise rejections
246
+
247
+ Recorded as errors (severity `error`, `autter.unhandled_rejection: true`, not
248
+ fatal) — without changing whether your process crashes. Any
249
+ `unhandledRejection` listener switches off Node's default crash, so the SDK
250
+ does what Node would have done when its listener is the only one:
251
+
252
+ | `--unhandled-rejections` | Your own listener? | What happens | Recorded as |
253
+ | --- | --- | --- | --- |
254
+ | `throw` (default) | no | rethrown on the next tick: exit code 1, as before | fatal crash (`autter.unhandled`) |
255
+ | `throw` (default) | yes | your listener decides | error |
256
+ | `strict` | either | Node crashes first, as before | fatal crash |
257
+ | `warn`, `none` | either | no crash, as before | error |
258
+ | `warn-with-error-code` | no | Node's warning and exit code 1, as before | error |
259
+
260
+ The one visible difference: after the rethrow, `uncaughtException` listeners
261
+ receive origin `"uncaughtException"` rather than `"unhandledRejection"`.
262
+ `captureUnhandledRejections: false` (or `captureGlobalErrors: false`) restores
263
+ the 1.5.0 behaviour, where a rejection is seen only when it crashes the
264
+ process.
265
+
170
266
  ### Version compatibility: checked for you
171
267
 
172
268
  Some features need a minimum ingester version: operation logging needs
@@ -288,6 +384,48 @@ This closes the server-side gap to match the browser relay's payload
288
384
  whitelist; it is best-effort scrubbing of obvious PII shapes, not a DLP
289
385
  engine — keep secrets out of attributes in the first place.
290
386
 
387
+ **Query strings (1.6.1+).** Query strings carry what a path never should:
388
+ emails, one-time codes, API keys, session ids, search terms. So no span
389
+ leaves the process with query values, whichever instrumentation made it
390
+ (HTTP server and client spans, undici/fetch, Express, Fastify, Next.js's own
391
+ spans):
392
+
393
+ - `http.target`, `http.url`, `url.full` and `url.original` are cut at the `?`
394
+ (and `#`): `/search?email=jane@example.com&page=2` becomes `/search`;
395
+ - `url.query` keeps only the parameter names, `email=[redacted]&page=[redacted]`.
396
+ It is added from the URL when the instrumentation did not set it, so you
397
+ still see which parameters a request used. A parameter without `=`
398
+ (`/verify?<token>`) may itself be the secret and is masked whole;
399
+ - span names lose their query too (Next.js names a request span
400
+ `GET /search?q=…` until it knows the route).
401
+
402
+ List the parameters whose values are safe to keep, such as pagination or
403
+ sorting (exact names, after percent-decoding; kept values are still scrubbed
404
+ by `redactAttributes`):
405
+
406
+ ```ts
407
+ initAutterServer({ ..., keepQueryParams: ["page", "sort"] });
408
+ // url.query: "email=[redacted]&page=2"
409
+ ```
410
+
411
+ This runs at export, like the scrubbing above, and also with
412
+ `redactAttributes: false`. Route templates (`http.route`) and request
413
+ summaries never contained a query.
414
+
415
+ ### One SDK per process, however many copies are loaded
416
+
417
+ Bundlers can load this package more than once: Next.js bundles one copy
418
+ into `instrumentation.ts` and another into each route (unless it is in
419
+ `serverExternalPackages`), and a process can load both the ESM and the CJS
420
+ build. Since 1.6.1 every copy shares one process-wide state
421
+ (`globalThis[Symbol.for("@autter/runtime-node")]`): the copy that ran
422
+ `initAutterServer` (or `initAutterLogging`) is the SDK for all of them, so
423
+ `withRuntimeRequest`, `captureException`, `runtimeLogger` and the rest export
424
+ through it from any copy, and an error one copy reported is never reported
425
+ again by another. Before any copy starts, and after `shutdown()`, every copy
426
+ behaves as before: records go to the console. The shared object is
427
+ versioned; a copy that finds a layout it does not know keeps its own state.
428
+
291
429
  ## 3. LLM tracing
292
430
 
293
431
  For the field contract, manual reporting patterns, and provider-agnostic
@@ -90,6 +90,35 @@ var compat_manifest_default = {
90
90
  ingester: "1.5.0",
91
91
  migrations: ["0013-occurrence-codes"],
92
92
  sdks: { "@autter/runtime-node": "1.5.0", "@autter/runtime-next": "1.5.0", "@autter/runtime-browser": "1.4.0", "@autter/runtime-edge": "1.0.0" }
93
+ },
94
+ {
95
+ id: "breadcrumbs",
96
+ title: "Breadcrumbs: the steps before an error",
97
+ ingester: "1.6.0",
98
+ migrations: [],
99
+ sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-node": "1.6.0", "@autter/runtime-next": "1.6.0" }
100
+ },
101
+ {
102
+ id: "source_map_debug_ids",
103
+ title: "Source map debug ids",
104
+ ingester: "1.6.0",
105
+ migrations: ["0017-source-maps-debug-id"],
106
+ route: "/v1/sourcemaps/check",
107
+ sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-next": "1.6.0" }
108
+ },
109
+ {
110
+ id: "fingerprint_override",
111
+ title: "Fingerprint override",
112
+ ingester: "1.6.0",
113
+ migrations: [],
114
+ sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-node": "1.6.0", "@autter/runtime-next": "1.6.0", "@autter/runtime-edge": "1.1.0" }
115
+ },
116
+ {
117
+ id: "browser_trace_propagation",
118
+ title: "Browser trace propagation (browser errors linked to server traces)",
119
+ ingester: "1.6.0",
120
+ migrations: [],
121
+ sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-next": "1.6.0", "@autter/runtime-node": "1.6.0" }
93
122
  }
94
123
  ]
95
124
  };
@@ -435,7 +464,7 @@ var _https = require('https');
435
464
  // package.json
436
465
  var package_default = {
437
466
  name: "@autter/runtime-node",
438
- version: "1.5.0",
467
+ version: "1.6.1",
439
468
  description: "Autter Runtime for Node.js: request wide events, coded errors, operation logging, same-origin browser relay and a curated OpenTelemetry server tracker",
440
469
  license: "MIT",
441
470
  type: "module",
@@ -486,6 +515,8 @@ var package_default = {
486
515
  "@autter/runtime-core": "0.0.0",
487
516
  "@types/node": "^22.10.0",
488
517
  express: "^4.21.2",
518
+ fastify: "^5.12.5",
519
+ koa: "^3.2.1",
489
520
  tsup: "^8.3.5",
490
521
  typescript: "^5.7.0"
491
522
  },
@@ -508,6 +539,55 @@ var package_default = {
508
539
  ]
509
540
  };
510
541
 
542
+ // src/shared.ts
543
+ var SHARED_STATE_KEY = /* @__PURE__ */ Symbol.for("@autter/runtime-node");
544
+ var SHARED_STATE_LAYOUT = 1;
545
+ function isObject(value) {
546
+ return typeof value === "object" && value !== null;
547
+ }
548
+ var VERSION = package_default.version;
549
+ function resolveShared() {
550
+ const fresh = { v: SHARED_STATE_LAYOUT, sdk: VERSION };
551
+ const host = globalThis;
552
+ let existing;
553
+ try {
554
+ existing = host[SHARED_STATE_KEY];
555
+ if (existing === void 0) {
556
+ Object.defineProperty(host, SHARED_STATE_KEY, {
557
+ value: fresh,
558
+ configurable: true,
559
+ enumerable: false,
560
+ writable: false
561
+ });
562
+ return fresh;
563
+ }
564
+ } catch (e) {
565
+ return fresh;
566
+ }
567
+ if (isObject(existing) && existing.v === SHARED_STATE_LAYOUT) return existing;
568
+ debugLog(
569
+ `another copy of @autter/runtime-node uses shared state layout ${String(isObject(existing) ? existing.v : typeof existing)}; this copy (${VERSION}, layout ${SHARED_STATE_LAYOUT}) keeps its own state`
570
+ );
571
+ return fresh;
572
+ }
573
+ var shared = resolveShared();
574
+ function sharedSlot(name, init) {
575
+ const fresh = init();
576
+ try {
577
+ const existing = shared[name];
578
+ if (!isObject(existing)) {
579
+ shared[name] = fresh;
580
+ return fresh;
581
+ }
582
+ for (const [key, value] of Object.entries(fresh)) {
583
+ if (existing[key] === void 0) existing[key] = value;
584
+ }
585
+ return existing;
586
+ } catch (e2) {
587
+ return fresh;
588
+ }
589
+ }
590
+
511
591
  // src/compat.ts
512
592
  var SDK_IDENTITY = {
513
593
  name: "@autter/runtime-node",
@@ -556,7 +636,7 @@ function compatRequest(method, url, options = {}) {
556
636
  if (options.unref !== false) req.on("socket", (socket) => socket.unref());
557
637
  req.on("error", () => done(null));
558
638
  req.end(options.body);
559
- } catch (e) {
639
+ } catch (e3) {
560
640
  done(null);
561
641
  }
562
642
  });
@@ -569,7 +649,7 @@ async function fetchIngesterCompat(endpoint, options = {}) {
569
649
  let report = null;
570
650
  try {
571
651
  report = JSON.parse(res.body);
572
- } catch (e2) {
652
+ } catch (e4) {
573
653
  }
574
654
  const info = _nullishCoalesce(ingesterInfoFromReport(report), () => ( headerInfo(res.headers)));
575
655
  return info ? { info, report } : { info: null, report, error: "unrecognised /v1/compat response" };
@@ -600,7 +680,7 @@ function envDisabled() {
600
680
  return value === "0" || value === "false" || value === "off";
601
681
  }
602
682
  var defaultWarn = (message) => console.warn(`[autter-runtime] ${message}`);
603
- var state = freshState(null, false);
683
+ var compat = sharedSlot("compat", () => ({ state: freshState(null, false) }));
604
684
  function freshState(endpoint, enabled, sdk = SDK_IDENTITY, warn = defaultWarn) {
605
685
  return {
606
686
  endpoint,
@@ -615,7 +695,7 @@ function freshState(endpoint, enabled, sdk = SDK_IDENTITY, warn = defaultWarn) {
615
695
  };
616
696
  }
617
697
  function configureCompatCheck(options) {
618
- state = freshState(
698
+ compat.state = freshState(
619
699
  options.endpoint.replace(/\/$/, ""),
620
700
  options.enabled !== false && !envDisabled(),
621
701
  _nullishCoalesce(options.sdk, () => ( SDK_IDENTITY)),
@@ -623,23 +703,23 @@ function configureCompatCheck(options) {
623
703
  );
624
704
  }
625
705
  function ensureCompatConfigured(endpoint) {
626
- if (state.endpoint === null && !envDisabled()) {
627
- state = freshState(endpoint.replace(/\/$/, ""), true);
706
+ if (compat.state.endpoint === null && !envDisabled()) {
707
+ compat.state = freshState(endpoint.replace(/\/$/, ""), true);
628
708
  }
629
709
  }
630
710
  function noteCompatFeature(id) {
631
711
  try {
632
- if (!state.enabled || state.features.has(id) || !featureById(id)) return;
633
- state.features.add(id);
634
- if (state.info) {
712
+ if (!compat.state.enabled || compat.state.features.has(id) || !featureById(id)) return;
713
+ compat.state.features.add(id);
714
+ if (compat.state.info) {
635
715
  evaluateAndWarn();
636
716
  return;
637
717
  }
638
- if (state.started) return;
639
- state.started = true;
640
- const current = state;
718
+ if (compat.state.started) return;
719
+ compat.state.started = true;
720
+ const current = compat.state;
641
721
  setTimeout(() => void runCheck(current), 0).unref();
642
- } catch (e3) {
722
+ } catch (e5) {
643
723
  }
644
724
  }
645
725
  async function runCheck(current) {
@@ -648,7 +728,7 @@ async function runCheck(current) {
648
728
  const { info, error } = await fetchIngesterCompat(current.endpoint, {
649
729
  probeRoutes: routesFor(current.features)
650
730
  });
651
- if (state !== current) return;
731
+ if (compat.state !== current) return;
652
732
  if (!info) {
653
733
  debugLog(`compat check skipped: ${_nullishCoalesce(error, () => ( "ingester version unknown"))}`);
654
734
  return;
@@ -672,8 +752,8 @@ function routesFor(features) {
672
752
  return routes;
673
753
  }
674
754
  function mergeInfo(next) {
675
- const prev = state.info;
676
- state.info = {
755
+ const prev = compat.state.info;
756
+ compat.state.info = {
677
757
  version: _nullishCoalesce(_nullishCoalesce(next.version, () => ( _optionalChain([prev, 'optionalAccess', _21 => _21.version]))), () => ( null)),
678
758
  ...next.legacy || _optionalChain([prev, 'optionalAccess', _22 => _22.legacy]) ? { legacy: true } : {},
679
759
  ..._nullishCoalesce(next.schema, () => ( _optionalChain([prev, 'optionalAccess', _23 => _23.schema]))) ? { schema: _nullishCoalesce(next.schema, () => ( _optionalChain([prev, 'optionalAccess', _24 => _24.schema]))) } : {},
@@ -681,17 +761,17 @@ function mergeInfo(next) {
681
761
  };
682
762
  }
683
763
  function evaluateAndWarn() {
684
- const info = state.info;
764
+ const info = compat.state.info;
685
765
  if (!info) return;
686
- if (info.version === null && state.endpoint) {
687
- const unprobed = routesFor(state.features).filter(
688
- (route) => _optionalChain([info, 'access', _26 => _26.routes, 'optionalAccess', _27 => _27[route]]) === void 0 && !state.pendingProbes.has(route)
766
+ if (info.version === null && compat.state.endpoint) {
767
+ const unprobed = routesFor(compat.state.features).filter(
768
+ (route) => _optionalChain([info, 'access', _26 => _26.routes, 'optionalAccess', _27 => _27[route]]) === void 0 && !compat.state.pendingProbes.has(route)
689
769
  );
690
770
  if (unprobed.length) {
691
- const current = state;
771
+ const current = compat.state;
692
772
  for (const route of unprobed) current.pendingProbes.add(route);
693
773
  void Promise.all(unprobed.map((route) => probeRoute(current.endpoint, route, {}).then((exists) => [route, exists]))).then((results) => {
694
- if (state !== current) return;
774
+ if (compat.state !== current) return;
695
775
  const routes = {};
696
776
  for (const [route, exists] of results) if (exists !== null) routes[route] = exists;
697
777
  mergeInfo({ version: null, legacy: true, routes });
@@ -700,56 +780,56 @@ function evaluateAndWarn() {
700
780
  });
701
781
  }
702
782
  }
703
- for (const issue of evaluateCompat({ features: state.features, ingester: info })) {
783
+ for (const issue of evaluateCompat({ features: compat.state.features, ingester: info })) {
704
784
  warnOnce(issue);
705
785
  }
706
786
  }
707
787
  function warnOnce(issue) {
708
788
  const key = `${issue.feature}:${issue.kind}`;
709
- if (state.warned.has(key)) return;
710
- state.warned.add(key);
789
+ if (compat.state.warned.has(key)) return;
790
+ compat.state.warned.add(key);
711
791
  try {
712
- state.warn(issue.message);
713
- } catch (e4) {
792
+ compat.state.warn(issue.message);
793
+ } catch (e6) {
714
794
  }
715
795
  }
716
796
  function observeIngesterResponse(response) {
717
797
  try {
718
- if (!state.enabled) return;
798
+ if (!compat.state.enabled) return;
719
799
  const fromHeader = headerInfo(response.headers);
720
800
  if (fromHeader) {
721
- if (_optionalChain([state, 'access', _28 => _28.info, 'optionalAccess', _29 => _29.version]) !== fromHeader.version) {
801
+ if (_optionalChain([compat, 'access', _28 => _28.state, 'access', _29 => _29.info, 'optionalAccess', _30 => _30.version]) !== fromHeader.version) {
722
802
  mergeInfo(fromHeader);
723
803
  evaluateAndWarn();
724
804
  }
725
805
  return;
726
806
  }
727
- if (response.status === 404 && response.route && !_optionalChain([state, 'access', _30 => _30.info, 'optionalAccess', _31 => _31.version])) {
807
+ if (response.status === 404 && response.route && !_optionalChain([compat, 'access', _31 => _31.state, 'access', _32 => _32.info, 'optionalAccess', _33 => _33.version])) {
728
808
  mergeInfo({ version: null, legacy: true, routes: { [response.route]: false } });
729
809
  evaluateAndWarn();
730
810
  }
731
- } catch (e5) {
811
+ } catch (e7) {
732
812
  }
733
813
  }
734
814
  function noteRelayRejection(eventTypes, status, headers) {
735
815
  try {
736
- if (!state.enabled || status !== 400 || headerInfo(headers)) return [];
816
+ if (!compat.state.enabled || status !== 400 || headerInfo(headers)) return [];
737
817
  const named = [];
738
818
  for (const id of featuresForBrowserEvents(eventTypes)) {
739
819
  const feature = featureById(id);
740
820
  const key = `${id}:relay_rejected`;
741
- if (state.warned.has(key)) continue;
742
- state.warned.add(key);
821
+ if (compat.state.warned.has(key)) continue;
822
+ compat.state.warned.add(key);
743
823
  named.push(id);
744
824
  try {
745
- state.warn(
825
+ compat.state.warn(
746
826
  `The ingester rejected a browser batch containing ${feature.title.toLowerCase()} events. ${feature.title} needs ingester >= ${feature.ingester}; yours is 1.4.0 or older (it predates version reporting), so those batches are dropped. ${ingesterUpgradeHint(COMPAT_MANIFEST)}`
747
827
  );
748
- } catch (e6) {
828
+ } catch (e8) {
749
829
  }
750
830
  }
751
831
  return named;
752
- } catch (e7) {
832
+ } catch (e9) {
753
833
  return [];
754
834
  }
755
835
  }
@@ -776,4 +856,5 @@ function noteRelayRejection(eventTypes, status, headers) {
776
856
 
777
857
 
778
858
 
779
- exports.COMPAT_MANIFEST = COMPAT_MANIFEST; exports.INGESTER_VERSION_HEADER = INGESTER_VERSION_HEADER; exports.versionAtLeast = versionAtLeast; exports.featuresForBrowserEvents = featuresForBrowserEvents; exports.evaluateCompat = evaluateCompat; exports.setDebugMode = setDebugMode; exports.isDebugEnabled = isDebugEnabled; exports.debugLog = debugLog; exports.telemetryStats = telemetryStats; exports.CountingExporter = CountingExporter; exports.registerFlushTarget = registerFlushTarget; exports.unregisterFlushTargets = unregisterFlushTargets; exports.installAutterAutoFlush = installAutterAutoFlush; exports.SDK_IDENTITY = SDK_IDENTITY; exports.compatRequest = compatRequest; exports.fetchIngesterCompat = fetchIngesterCompat; exports.configureCompatCheck = configureCompatCheck; exports.ensureCompatConfigured = ensureCompatConfigured; exports.noteCompatFeature = noteCompatFeature; exports.observeIngesterResponse = observeIngesterResponse; exports.noteRelayRejection = noteRelayRejection;
859
+
860
+ exports.COMPAT_MANIFEST = COMPAT_MANIFEST; exports.INGESTER_VERSION_HEADER = INGESTER_VERSION_HEADER; exports.versionAtLeast = versionAtLeast; exports.featuresForBrowserEvents = featuresForBrowserEvents; exports.evaluateCompat = evaluateCompat; exports.setDebugMode = setDebugMode; exports.isDebugEnabled = isDebugEnabled; exports.debugLog = debugLog; exports.telemetryStats = telemetryStats; exports.CountingExporter = CountingExporter; exports.registerFlushTarget = registerFlushTarget; exports.unregisterFlushTargets = unregisterFlushTargets; exports.installAutterAutoFlush = installAutterAutoFlush; exports.sharedSlot = sharedSlot; exports.SDK_IDENTITY = SDK_IDENTITY; exports.compatRequest = compatRequest; exports.fetchIngesterCompat = fetchIngesterCompat; exports.configureCompatCheck = configureCompatCheck; exports.ensureCompatConfigured = ensureCompatConfigured; exports.noteCompatFeature = noteCompatFeature; exports.observeIngesterResponse = observeIngesterResponse; exports.noteRelayRejection = noteRelayRejection;