@flareapp/core 2.7.0 → 2.9.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/dist/index.d.mts CHANGED
@@ -1,222 +1,20 @@
1
- //#region src/types.d.ts
2
- type MessageLevel = 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency';
3
- type AttributeValue = string | number | boolean | null | AttributeValue[] | {
4
- [key: string]: AttributeValue;
5
- };
6
- type Attributes = Record<string, AttributeValue>;
7
- /**
8
- * An identified user passed to `Flare.setUser`. The four known fields project to the
9
- * report keys the Flare backend reads: `id`→`user.id`, `email`→`user.email`,
10
- * `fullName`→`user.full_name`, `ipAddress`→`client.address`. Any OTHER key is bundled
11
- * into `user.attributes`.
12
- *
13
- * Caveat: the open index signature means a misspelled known field (e.g. `fullname` or
14
- * `full_name` instead of `fullName`) does NOT raise a type error — it silently lands in
15
- * `user.attributes` rather than the identity key. Spell the four known fields exactly.
16
- */
17
- type User = {
18
- id?: string | number;
19
- email?: string;
20
- fullName?: string;
21
- ipAddress?: string;
22
- [key: string]: AttributeValue | undefined;
23
- };
24
- type Config = {
25
- key: string | null;
26
- version: string;
27
- sourcemapVersionId: string;
28
- stage: string;
29
- maxGlowsPerReport: number;
30
- reportBrowserExtensionErrors: boolean;
31
- ingestUrl: string;
32
- debug: boolean;
33
- urlDenylist: RegExp;
34
- replaceDefaultUrlDenylist: boolean;
35
- sampleRate: number;
36
- enableLogs: boolean;
37
- logsIngestUrl: string;
38
- minimumLogLevel?: MessageLevel;
39
- serviceName?: string;
40
- maxLogBufferSize: number;
41
- logFlushIntervalMs: number;
42
- logFlushMaxBytes: number;
43
- keepaliveMaxBytes: number;
44
- beforeEvaluate: (error: Error) => Error | false | null | Promise<Error | false | null>;
45
- beforeSubmit: (report: Report) => Report | false | null | Promise<Report | false | null>;
46
- };
47
- type StackFrame = {
48
- file: string;
49
- lineNumber: number;
50
- columnNumber?: number;
51
- method?: string;
52
- class?: string;
53
- codeSnippet?: {
54
- [line: number]: string;
55
- };
56
- isApplicationFrame?: boolean;
57
- arguments?: unknown[];
58
- };
59
- type SpanEvent = {
60
- type: string;
61
- startTimeUnixNano: number;
62
- endTimeUnixNano: number | null;
63
- attributes: Attributes;
64
- };
65
- type OverriddenGrouping = 'exception_class' | 'exception_message' | 'exception_message_and_class' | 'full_stacktrace_and_exception_class_and_code';
66
- type Report = {
67
- exceptionClass?: string | null;
68
- message?: string | null;
69
- code?: string;
70
- seenAtUnixNano: number;
71
- isLog?: boolean;
72
- level?: MessageLevel;
73
- sourcemapVersionId?: string;
74
- trackingUuid?: string;
75
- handled?: boolean;
76
- openFrameIndex?: number;
77
- applicationPath?: string;
78
- overriddenGrouping?: OverriddenGrouping | null;
79
- stacktrace: StackFrame[];
80
- events: SpanEvent[];
81
- attributes: Attributes;
82
- };
83
- type Glow = {
84
- time: number;
85
- microtime: number;
86
- name: string;
87
- messageLevel: MessageLevel;
88
- metaData: Record<string, unknown> | Record<string, unknown>[];
89
- };
90
- type EntryPointHandler = {
91
- identifier?: string;
92
- name?: string;
93
- type?: string;
94
- };
95
- type SdkInfo = {
96
- name: string;
97
- version: string;
98
- };
99
- type Framework = {
100
- name: string;
101
- version?: string;
102
- };
103
- type AnyValue = {
104
- stringValue: string;
105
- } | {
106
- boolValue: boolean;
107
- } | {
108
- intValue: number;
109
- } | {
110
- doubleValue: number;
111
- } | {
112
- arrayValue: {
113
- values: AnyValue[];
114
- };
115
- } | {
116
- kvlistValue: {
117
- values: KeyValue[];
118
- };
119
- };
120
- type KeyValue = {
121
- key: string;
122
- value: AnyValue;
123
- };
124
- type OtelResource = {
125
- attributes: KeyValue[];
126
- droppedAttributesCount: number;
127
- };
128
- type OtelScope = {
129
- name: string;
130
- version: string;
131
- attributes: KeyValue[];
132
- droppedAttributesCount: number;
133
- };
134
- type OtelLogRecord = {
135
- timeUnixNano: string;
136
- observedTimeUnixNano: string;
137
- severityNumber: number;
138
- severityText: string;
139
- body: AnyValue;
140
- attributes: KeyValue[];
141
- flags: number;
142
- droppedAttributesCount: number;
143
- };
144
- type LogsEnvelope = {
145
- resourceLogs: Array<{
146
- resource: OtelResource;
147
- scopeLogs: Array<{
148
- scope: OtelScope;
149
- logRecords: OtelLogRecord[];
150
- }>;
151
- }>;
152
- };
153
- type BufferedLog = {
154
- timeUnixNano: string;
155
- severityNumber: number;
156
- severityText: string;
157
- message: string;
158
- recordAttributes: KeyValue[];
159
- resourceAttributes: Attributes;
160
- };
161
- //#endregion
162
- //#region src/util/assert.d.ts
163
- declare function assert(value: unknown, message: string, debug: boolean): boolean;
164
- //#endregion
165
- //#region src/util/assertKey.d.ts
166
- declare function assertKey(key: unknown, debug: boolean): boolean;
167
- //#endregion
168
- //#region src/util/convertToError.d.ts
169
- declare function convertToError(error: unknown): Error;
170
- //#endregion
171
- //#region src/util/extractCode.d.ts
172
- declare function extractCode(error: Error): string | undefined;
173
- //#endregion
174
- //#region src/util/flatJsonStringify.d.ts
175
- declare function flatJsonStringify(json: object): string;
176
- //#endregion
177
- //#region src/util/glowsToEvents.d.ts
178
- declare function glowsToEvents(glows: Glow[]): SpanEvent[];
179
- //#endregion
180
- //#region src/util/now.d.ts
181
- declare function now(): number;
182
- //#endregion
183
- //#region src/util/redactUrl.d.ts
184
- declare const DEFAULT_URL_DENYLIST: RegExp;
185
- declare function resolveDenylist(custom?: RegExp, replaceDefault?: boolean, defaultDenylist?: RegExp): RegExp;
186
- declare function redactUrlQuery(fullPath: string, denylist?: RegExp): string;
187
- //#endregion
188
- //#region src/util/rejection.d.ts
189
- /**
190
- * Shared unhandled-rejection routing. A rejection "reason" can be anything a
191
- * promise was rejected with: an Error, an Error-like object carrying a `.stack`,
192
- * a string, or a plain object. The browser `unhandledrejection` listener
193
- * (`@flareapp/js`) and the React Native engine rejection tracker
194
- * (`@flareapp/react-native`) need the SAME routing so a report looks identical
195
- * across SDKs, so it lives here instead of being copy-pasted (and drifting) per
196
- * client.
197
- */
198
- type RejectionReporter = {
199
- reportSilently: (error: Error) => void;
200
- reportUnhandledRejection: (message: string) => unknown;
201
- };
202
- /** Best-effort human-readable description of an arbitrary rejection reason. */
203
- declare function describeRejectionReason(reason: unknown): string;
204
- /**
205
- * Route a rejection reason to the reporter: an Error (or any stack-bearing
206
- * object) goes to `reportSilently` so the STACK survives; only a stackless
207
- * reason falls back to `reportUnhandledRejection` (string message, empty-stack
208
- * `UnhandledRejection` class). Any rejection from `reportUnhandledRejection`'s
209
- * returned promise is swallowed so a transport failure cannot itself surface as
210
- * an unhandled rejection. `reportSilently` is assumed not to throw synchronously
211
- * (core's does its work asynchronously); it is intentionally not wrapped, so a
212
- * synchronous throw there would propagate.
213
- */
214
- declare function routeRejection(reporter: RejectionReporter, reason: unknown): void;
215
- //#endregion
1
+ import { $ as TracesSampler, A as BufferedLog, B as OtelLogRecord, D as AnyValue, E as assert, F as Framework, G as SdkInfo, H as OverriddenGrouping, I as Glow, J as SpanOptions, K as Span, L as KeyValue, M as Config, N as EntryPointHandler, O as AttributeValue, P as EntryPointType, Q as TracesEnvelope, R as LogsEnvelope, S as convertToError, T as assertKey, U as Report, V as OtelSpan, W as SamplingContext, X as SpanStatusCode, Y as SpanStatus, Z as StackFrame, _ as glowsToEvents, a as SafeCloneOptions, b as SdkTaggable, c as describeRejectionReason, d as redactObjectValues, et as User, f as redactUrlQuery, h as now, it as FrameworkName, j as BufferedSpan, k as Attributes, l as routeRejection, m as safeDecode, n as urlAttributes, nt as BrowserSpanType, o as safeClone, p as resolveDenylist, q as SpanEvent, r as toCustomContext, rt as SpanTypeName, s as RejectionReporter, tt as BrowserSpanEventType, u as DEFAULT_URL_DENYLIST, v as flatJsonStringify, x as createIdentityTagger, y as extractCode, z as MessageLevel } from "./urlAttributes-CYJKlJKi.mjs";
2
+
216
3
  //#region src/api/Api.d.ts
217
4
  declare class Api {
5
+ private pendingKeepaliveBytes;
6
+ private pendingKeepaliveRequests;
7
+ /**
8
+ * How many keepalive bytes are still available. Logs and traces share one browser allowance and both
9
+ * flush on page hide, so whichever goes second has to pack against what is left rather than assume the
10
+ * whole budget, or it exceeds the gate in send() and silently degrades to a cancellable fetch.
11
+ */
12
+ keepaliveBudgetRemaining(): number;
218
13
  report(report: Report, url: string, key: string | null, reportBrowserExtensionErrors: boolean, debug?: boolean): Promise<void>;
219
14
  logs(envelope: LogsEnvelope, url: string, key: string | null, debug?: boolean, keepalive?: boolean): Promise<void>;
15
+ traces(envelope: TracesEnvelope, url: string, key: string | null, debug?: boolean, keepalive?: boolean): Promise<void>;
16
+ private ingestHeaders;
17
+ private send;
220
18
  }
221
19
  //#endregion
222
20
  //#region src/logging/FlushScheduler.d.ts
@@ -250,10 +48,8 @@ type LoggerDeps = {
250
48
  };
251
49
  declare class Logger {
252
50
  private deps;
253
- private buffer;
51
+ private inner;
254
52
  private resourceAttributes;
255
- private timer;
256
- private timerActive;
257
53
  constructor(deps: LoggerDeps);
258
54
  debug(message: string, context?: Attributes, attributes?: Attributes): void;
259
55
  info(message: string, context?: Attributes, attributes?: Attributes): void;
@@ -264,108 +60,50 @@ declare class Logger {
264
60
  alert(message: string, context?: Attributes, attributes?: Attributes): void;
265
61
  emergency(message: string, context?: Attributes, attributes?: Attributes): void;
266
62
  bufferLength(): number;
267
- private record;
268
- private evaluateTriggers;
269
- private armTimer;
270
- private trim;
271
63
  flush(opts?: {
272
64
  keepalive?: boolean;
273
65
  }): void;
274
66
  clear(): void;
275
- private packForKeepalive;
276
- private buildEnvelope;
67
+ private record;
277
68
  private resourceForFlush;
278
- private clearTimer;
279
69
  private estimateBytes;
280
- private bufferBytes;
281
70
  }
282
71
  //#endregion
283
72
  //#region src/Scope.d.ts
284
- /**
285
- * The report attribute keys that `Flare.setUser` owns: the four projected identity
286
- * fields plus the `user.attributes` bag for extras. Single source of truth so the
287
- * clear pass and the set pass in `setUser` cannot drift, and so consumers that must
288
- * stamp identity outside core's report pipeline (Electron's forwarded-renderer path)
289
- * pick up the exact same set instead of re-hardcoding it.
290
- */
73
+ /** Every key `Flare.setUser` owns. Consumers stamping identity outside core's report pipeline (Electron's
74
+ * forwarded-renderer path) reuse this exact set. */
291
75
  declare const USER_IDENTITY_KEYS: readonly [...("user.id" | "user.email" | "user.full_name" | "client.address")[], "user.attributes"];
292
- /**
293
- * Pick the user-identity attributes currently set on a scope. Used where identity must
294
- * be copied onto a report that does not flow through `Flare.report()` (which would
295
- * otherwise spread `pendingAttributes` automatically).
296
- */
76
+ /** For reports that do not flow through `Flare.report()`, which would spread `pendingAttributes` itself. */
297
77
  declare function userIdentityAttributes(scope: Scope): Attributes;
298
78
  /**
299
- * Holds the per-call mutable state that used to live on the `Flare` instance:
300
- * breadcrumbs (`glows`), custom attributes (`pendingAttributes`), and the
301
- * current entry-point handler.
302
- *
303
- * Why this exists as its own class: in the browser there is one `Flare` per
304
- * page and one user at a time, so a single shared bag of state is fine. In
305
- * Node, a single `Flare` instance serves many concurrent requests, and each
306
- * request wants its own breadcrumbs and its own custom context that do NOT
307
- * leak into other requests. Splitting this state out of `Flare` lets the
308
- * consumer choose: one global `Scope` (browser) or one `Scope` per request
309
- * via AsyncLocalStorage (Node).
310
- *
311
- * `Flare` reads and writes this through `scopeProvider.active()` instead of
312
- * holding the state directly, so the per-request behavior comes from the
313
- * provider, not from the class itself.
314
- *
315
- * `NodeScope` (in `@flareapp/node`) extends this with a `request` bucket
316
- * (HTTP method, path, headers). User identity is written to `pendingAttributes`
317
- * by `Flare.setUser`, so it needs no dedicated field. Browser does not need `request`.
79
+ * Per-call mutable state, split out of `Flare` so the consumer can choose one global `Scope` (browser, one
80
+ * user at a time) or one per request via AsyncLocalStorage (Node, where concurrent requests must not leak
81
+ * into each other). `@flareapp/node`'s `NodeScope` extends this with a `request` bucket.
318
82
  */
319
83
  declare class Scope {
320
84
  glows: Glow[];
85
+ breadcrumbs: SpanEvent[];
321
86
  pendingAttributes: Attributes;
322
87
  entryPoint: EntryPointHandler | null;
323
- /**
324
- * Append a breadcrumb. Caps the list at `maxGlowsPerReport` by dropping the
325
- * OLDEST entries when the limit is exceeded; this keeps reports below a
326
- * payload-size threshold while preserving the most recent events leading
327
- * up to an error.
328
- *
329
- * `slice(length - max)` returns the trailing `max` items, which is the
330
- * shortest way to drop from the front and keep insertion order.
331
- */
88
+ /** Caps at `maxGlowsPerReport` by dropping the oldest, so the payload stays bounded. */
332
89
  addGlow(glow: Glow, maxGlowsPerReport: number): void;
333
90
  clearGlows(): void;
334
- /**
335
- * Set a single attribute on this scope. Called from `Flare.addContext` and
336
- * `Flare.addContextGroup`. Last write wins.
337
- */
91
+ /** Drops the oldest when full. */
92
+ addBreadcrumb(breadcrumb: SpanEvent, maxBreadcrumbs: number): void;
93
+ clearBreadcrumbs(): void;
338
94
  setAttribute(key: string, value: AttributeValue): void;
339
- /**
340
- * Shallow-merge a bag of attributes into this scope. Used by Node's
341
- * AsyncLocalStorage provider when patching the live request context via
342
- * `flare.mergeContext({ ... })`. Last write wins per key; nested objects
343
- * are NOT deep-merged.
344
- */
95
+ /** Shallow: last write wins per key, nested objects are not deep-merged. */
345
96
  mergeAttributes(partial: Attributes): void;
346
97
  }
347
98
  /**
348
- * The seam through which `Flare` reaches its current `Scope`. Implementations
349
- * decide what "current" means.
350
- *
351
- * - `GlobalScopeProvider` always returns the same `Scope` instance (browser).
352
- * - `AsyncLocalStorageScopeProvider` in `@flareapp/node` returns the per-request
353
- * `NodeScope` stored in `node:async_hooks` for the in-flight async chain,
354
- * falling back to a single shared scope when called outside any
355
- * `runWithContext(...)` callback.
356
- *
357
- * Any consumer of `@flareapp/core` can supply its own provider to plug in
358
- * different "current scope" semantics.
99
+ * The seam through which `Flare` reaches its current `Scope`; implementations decide what "current" means.
100
+ * `@flareapp/node`'s returns the per-request `NodeScope` from `node:async_hooks`, falling back to a shared
101
+ * scope outside any `runWithContext(...)`.
359
102
  */
360
103
  interface ScopeProvider {
361
104
  active(): Scope;
362
105
  }
363
- /**
364
- * The simplest provider: one `Scope` for the lifetime of the provider, shared
365
- * by every caller. This is the right default for environments with a single
366
- * logical context (browser tab, CLI script, etc.) and is the default that
367
- * `Flare`'s constructor falls back to when no provider is supplied.
368
- */
106
+ /** One `Scope` for the provider's lifetime. The right default for a browser tab or a CLI script. */
369
107
  declare class GlobalScopeProvider implements ScopeProvider {
370
108
  private scope;
371
109
  active(): Scope;
@@ -385,6 +123,102 @@ type ReaderResponse = {
385
123
  declare function getCodeSnippet(fileReader: FileReader, url?: string, lineNumber?: number, columnNumber?: number): Promise<ReaderResponse>;
386
124
  declare function readLinesFromFile(fileText: string, lineNumber: number, columnNumber?: number, maxSnippetLineLength?: number, maxSnippetLines?: number): ReaderResponse;
387
125
  //#endregion
126
+ //#region src/tracing/context.d.ts
127
+ interface ActiveSpanHolder {
128
+ getActive(): Span | undefined;
129
+ /**
130
+ * Run `fn` with `span` active, restoring the prior active span afterward. A callback (not a bare setter) so a Node
131
+ * holder can back it with AsyncLocalStorage.run(...) to preserve async-scoped context.
132
+ */
133
+ withActive<T>(span: Span, fn: () => T): T;
134
+ /**
135
+ * Persistent "active root" that getActive() falls back to when no withActive scope is on the stack. Used by
136
+ * long-lived pageload/navigation roots so child spans (e.g. fetches) auto-parent to them. Optional; a holder that
137
+ * omits it simply has no active-root support.
138
+ */
139
+ setActiveRoot?(span: Span | undefined): void;
140
+ }
141
+ declare class InMemoryActiveSpanHolder implements ActiveSpanHolder {
142
+ private active;
143
+ private root;
144
+ getActive(): Span | undefined;
145
+ withActive<T>(span: Span, fn: () => T): T;
146
+ setActiveRoot(span: Span | undefined): void;
147
+ }
148
+ //#endregion
149
+ //#region src/tracing/Tracer.d.ts
150
+ declare function defaultNowNano(): number;
151
+ type SpanPhase = 'start' | 'end';
152
+ type SpanLifecycleEvent = {
153
+ phase: SpanPhase;
154
+ span: Span;
155
+ };
156
+ type SpanLifecycleListener = (event: SpanLifecycleEvent) => void;
157
+ /** Bounded backstop for the live TraceState map: an app that never ends spans must not grow it forever. */
158
+ declare const DEFAULT_MAX_LIVE_TRACES = 1000;
159
+ type TracerDeps = {
160
+ api: Api;
161
+ getConfig: () => Config;
162
+ getSdkInfo: () => SdkInfo;
163
+ getFramework: () => Framework | null;
164
+ getScopeAttributes: () => Attributes;
165
+ getResourceAttributes: () => Attributes;
166
+ track: <T>(p: Promise<T>) => Promise<T>;
167
+ scheduler: FlushScheduler;
168
+ activeSpanHolder?: ActiveSpanHolder;
169
+ now?: () => number;
170
+ rng?: () => number;
171
+ maxLiveTraces?: number;
172
+ };
173
+ declare class Tracer {
174
+ private deps;
175
+ private buffer;
176
+ private holder;
177
+ private traceStates;
178
+ private closedTraces;
179
+ private stateGeneration;
180
+ private now;
181
+ private rng;
182
+ private maxLiveTraces;
183
+ private epoch;
184
+ private pendingContinuation;
185
+ private spanListeners;
186
+ constructor(deps: TracerDeps);
187
+ getActiveSpan(): Span | undefined;
188
+ setActiveRoot(span?: Span): void;
189
+ /**
190
+ * Take one span against `traceId`'s cap up front, for a caller that publishes a span id before the
191
+ * span exists (the component profilers do; their descendants record first). False means the trace is
192
+ * full and the caller should stay transparent instead of handing out an id the cap will refuse.
193
+ * Consumed by the matching `startSpan({ claimed: true })`.
194
+ */
195
+ claimSpanSlot(traceId: string): boolean;
196
+ addSpanListener(fn: SpanLifecycleListener): () => void;
197
+ private emitSpanEvent;
198
+ flush(opts?: {
199
+ keepalive?: boolean;
200
+ }): void;
201
+ clear(): void;
202
+ continueFromTraceparent(header: string): void;
203
+ /**
204
+ * Runs `fn` with the span active, so spans started inside auto-parent to it, then ends it.
205
+ * Records an error status first if `fn` throws or its returned promise rejects.
206
+ */
207
+ withSpan<T>(name: string, fn: (span: Span) => T, opts?: SpanOptions): T;
208
+ /** Starts a span the caller must end. Unlike `withSpan`, it does not become the active span,
209
+ * so spans started after it do not auto-parent to it. */
210
+ startSpan(name: string, opts?: SpanOptions): Span;
211
+ /** A real Span handle that records nothing, so callers never have to branch on whether tracing is on. */
212
+ private startInertSpan;
213
+ private resolveTrace;
214
+ private getOrSeedState;
215
+ private createState;
216
+ private makeSpan;
217
+ /** Bounded, LRU by insertion order, like traceStates. Holds primitives only, never a span. */
218
+ private rememberClosed;
219
+ private onSpanEnd;
220
+ }
221
+ //#endregion
388
222
  //#region src/Flare.d.ts
389
223
  type ContextCollector = (config: Readonly<Config>) => Attributes;
390
224
  declare class Flare {
@@ -394,171 +228,66 @@ declare class Flare {
394
228
  private scopeProvider;
395
229
  private inflight;
396
230
  private _logger;
231
+ private _tracer;
397
232
  private _config;
398
233
  private sdkInfo;
399
234
  private framework;
400
235
  /**
401
- * @param api sends the report over HTTP.
402
- * @param contextCollector returns per-report attributes (browser DOM info, Node
403
- * process info, etc). Default is a no-op.
404
- * @param fileReader reads source files for stack-trace snippets. Default
405
- * returns null (no snippets); `@flareapp/js` injects a
406
- * fetch-based reader, `@flareapp/node` injects a disk reader.
407
- * @param scopeProvider returns the current `Scope` (per-call mutable state:
408
- * glows, pendingAttributes, entryPoint). Browser uses a
409
- * single global scope; Node uses an AsyncLocalStorage-
410
- * backed provider so each request gets its own.
236
+ * @param api fetch transport for reports, logs and traces. Stateless: ingest url and
237
+ * key are passed per call, so tests swap in a fake.
238
+ * @param contextCollector per-report attributes (browser DOM, Node process). No-op by default.
239
+ * @param fileReader source files for stack-trace snippets. Defaults to no snippets;
240
+ * `@flareapp/js` injects a fetch reader, `@flareapp/node` a disk reader.
241
+ * @param scopeProvider the current `Scope`. Browser uses one global scope; Node an
242
+ * AsyncLocalStorage-backed provider so each request gets its own.
243
+ * @param scheduler drains the log and span buffers when the host's lifecycle ends (browser
244
+ * unload, process exit). No-op by default, leaving only size/timer flushes.
245
+ * @param activeSpanHolder tracks the active span so new spans auto-parent to it. In-memory by
246
+ * default; a platform can back it with AsyncLocalStorage instead.
411
247
  */
412
- constructor(api?: Api, contextCollector?: ContextCollector, fileReader?: FileReader, scopeProvider?: ScopeProvider, scheduler?: FlushScheduler);
248
+ constructor(api?: Api, contextCollector?: ContextCollector, fileReader?: FileReader, scopeProvider?: ScopeProvider, scheduler?: FlushScheduler, activeSpanHolder?: ActiveSpanHolder);
413
249
  /**
414
- * Register an in-flight report so `flush()` can wait for it. Called by
415
- * every public report entry point (`report`, `reportSilently`,
416
- * `reportMessage`, `reportUnhandledRejection`, `test`); each wraps its
417
- * full async pipeline (beforeEvaluate -> stack trace + source snippets ->
418
- * beforeSubmit -> `api.report()`) so the entire roundtrip is what's
419
- * tracked, not just the HTTP send at the end.
420
- *
421
- * Two problems this method solves at once.
422
- *
423
- * Problem 1: hold a reference to the work without leaking rejections.
424
- *
425
- * `p` is the real report pipeline; it can reject (network failure,
426
- * `beforeSubmit` throws, etc). If we stored `p` directly in `inflight`
427
- * and no caller attached a `.catch` (the global error listeners use
428
- * `reportSilently` which DOES catch, but the path is still subtle), an
429
- * eventual rejection would surface as an unhandled-rejection warning
430
- * on Node and a console error in the browser. Bad citizen.
431
- *
432
- * So we build a SHADOW promise that mirrors `p`'s timing but cannot
433
- * reject:
250
+ * Register an in-flight report so `flush()` can wait for it. Every entry point wraps its whole async
251
+ * pipeline, from beforeEvaluate through api.report, so flush() waits on all of it.
434
252
  *
435
- * p.then(
436
- * () => undefined, // on fulfilment, value is undefined
437
- * () => undefined, // on rejection, ALSO resolve with undefined
438
- * )
439
- *
440
- * Providing the second argument means we have "handled" any rejection
441
- * from `p`. The shadow always resolves with `undefined`, and `p`'s
442
- * rejection is consumed at the boundary. From the runtime's point of
443
- * view, the shadow is well-behaved.
444
- *
445
- * Problem 2: self-cleaning entry.
446
- *
447
- * `tracked.finally(() => this.inflight.delete(tracked))`. `finally`
448
- * fires whether the shadow resolves or rejects, but the shadow can no
449
- * longer reject (problem 1 normalized it), so this is effectively
450
- * "when the underlying report has settled, remove me from the Set."
451
- * No GC magic, no external cleanup, no race window.
452
- *
453
- * Note that `.finally` itself returns a new promise that we drop on
454
- * the floor. If the cleanup callback ever throws, that would surface
455
- * as an unhandled rejection on the dropped promise; `delete` does not
456
- * throw so we are safe today, but anything more elaborate added here
457
- * should be wrapped in try/catch.
458
- *
459
- * The return value is the ORIGINAL `p`. The caller awaits real success
460
- * or failure; the tracking is completely invisible to them. This is why
461
- * `await flare.report(err)` inside a fatal handler observes network
462
- * errors the same as before tracking was added.
253
+ * What goes in the Set is a shadow promise that mirrors `p`'s timing but cannot reject, so a failed
254
+ * report never surfaces as an unhandled rejection warning. `p` itself is returned untouched, so the
255
+ * caller still observes real success or failure.
463
256
  */
464
257
  private track;
465
258
  /**
466
- * Wait until every in-flight report settles, or until `timeoutMs`
467
- * elapses, whichever comes first. Always resolves; never rejects.
468
- *
469
- * The main consumer is `@flareapp/node`'s fatal handler:
470
- *
471
- * process.on('uncaughtException', async (err) => {
472
- * process.exitCode = 1;
473
- * try { await flare.report(err); } catch {}
474
- * await flare.flush(shutdownTimeoutMs);
475
- * process.exit(1);
476
- * });
477
- *
478
- * The fatal `report` is awaited explicitly; `flush` then drains any
479
- * OTHER reports that were already in flight (a request handler that
480
- * fired `flare.report(...)` concurrently with the crash). The timeout
481
- * caps the wait so a hung HTTP request cannot indefinitely block
482
- * shutdown.
483
- *
484
- * Walking the implementation:
485
- *
486
- * const pending = [...this.inflight];
487
- *
488
- * Spread takes a SNAPSHOT of the Set at this instant. Reports that
489
- * start AFTER this line are not included in `pending`, so they are
490
- * not awaited by THIS flush call. This is intentional: it bounds
491
- * the wait. Without the snapshot, a handler that kept emitting
492
- * reports during shutdown could keep flush alive forever and block
493
- * the process from exiting.
494
- *
495
- * if (pending.length === 0) return Promise.resolve();
496
- *
497
- * Fast path. No timer scheduled, no promise constructor needed.
498
- * Resolves on the microtask queue. Cheap.
499
- *
500
- * return new Promise<void>((resolve) => {
501
- * const timer = setTimeout(resolve, timeoutMs);
502
- * Promise.allSettled(pending).then(() => {
503
- * clearTimeout(timer);
504
- * resolve();
505
- * });
506
- * });
259
+ * Wait until every in-flight report settles or `timeoutMs` elapses. Always resolves, never rejects.
260
+ * Written for `@flareapp/node`'s fatal handler, which awaits the fatal report itself then flushes to
261
+ * drain any other concurrent reports before `process.exit`.
507
262
  *
508
- * The race between two outcomes, both calling the same `resolve`:
509
- *
510
- * 1. `setTimeout(resolve, timeoutMs)` schedules a "give up" call.
511
- * After `timeoutMs` it fires, calling `resolve()` from the
512
- * timer-queue side. The outer promise resolves immediately,
513
- * even if reports are still pending. Those reports are abandoned
514
- * (they continue running but the process is about to die).
515
- *
516
- * 2. `Promise.allSettled(pending)` returns a promise that resolves
517
- * when every promise in `pending` has either fulfilled or
518
- * rejected. It NEVER rejects on its own. We use `allSettled`
519
- * rather than `Promise.all` because `all` short-circuits on the
520
- * first rejection -- we want to wait for everyone regardless of
521
- * whether their HTTP calls succeed or fail. (Our shadows cannot
522
- * reject anyway because `track` normalized them, but using
523
- * `allSettled` documents the intent and survives future changes
524
- * to shadow construction.) When it resolves, we call
525
- * `clearTimeout(timer)` to cancel the pending timer (so it does
526
- * not fire later and call `resolve` a second time -- a no-op,
527
- * but wasted work) and then `resolve()` ourselves.
528
- *
529
- * Resolve can only meaningfully fire once. Subsequent calls to the
530
- * same `resolve` are silently ignored by the Promise spec, so the
531
- * race is safe even if for some reason both branches fired together.
532
- *
533
- * Things flush() deliberately does NOT do:
534
- *
535
- * - It does not reject. Even if every report failed, allSettled
536
- * resolves. Callers do not need a `.catch`.
537
- * - It does not retry. One pipeline attempt per report, then move on.
538
- * - It does not stop new reports from starting. The Flare instance
539
- * is still usable after flush resolves. flush is "wait for what is
540
- * in flight," not "freeze the SDK."
541
- * - It does not drain reports started after the snapshot. Call flush
542
- * again if you need to wait for those too.
263
+ * Snapshotting the Set bounds the wait: reports started after this line are not awaited, so a handler
264
+ * that keeps emitting during shutdown cannot block the process forever. Call flush again for those.
543
265
  */
544
266
  flush(timeoutMs?: number): Promise<void>;
545
267
  get config(): Readonly<Config>;
546
268
  get glows(): readonly Glow[];
547
269
  get logger(): Logger;
270
+ get tracer(): Tracer;
271
+ /** Starts a span the caller must end. Unlike `withSpan`, it does not become the active span,
272
+ * so spans started after it do not auto-parent to it. */
273
+ startSpan(name: string, opts?: SpanOptions): Span;
274
+ /**
275
+ * Runs `fn` with the span active, so spans started inside auto-parent to it, then ends it.
276
+ * Records an error status first if `fn` throws or its returned promise rejects.
277
+ */
278
+ withSpan<T>(name: string, fn: (span: Span) => T, opts?: SpanOptions): T;
548
279
  light(key?: string, debug?: boolean): this;
549
280
  configure(config: Partial<Config>): this;
550
281
  test(): Promise<void>;
551
282
  private testInternal;
552
283
  glow(name: string, level?: MessageLevel, data?: Record<string, unknown> | Record<string, unknown>[]): this;
284
+ protected addBreadcrumb(type: string, attributes: Attributes, startTimeUnixNano: number): void;
553
285
  clearGlows(): this;
554
286
  addContext(name: string, value: AttributeValue): this;
555
287
  addContextGroup(groupName: string, value: Record<string, AttributeValue>): this;
556
288
  /**
557
- * Attach an identified user to the active scope. Fields are projected to the
558
- * keys the Flare backend reads: `user.id`, `user.email`, `user.full_name`,
559
- * and `client.address`. Any extra keys are bundled into `user.attributes`.
560
- * Pass `null` to clear the user. Scope-aware: in Node this targets the
561
- * per-request scope via the scope provider.
289
+ * Maps the known fields onto the keys the Flare backend reads (see `USER_FIELD_KEYS`) and bundles
290
+ * anything else into `user.attributes`. Pass `null` to clear. In Node this targets the per-request scope.
562
291
  */
563
292
  setUser(user: User | null): this;
564
293
  setEntryPoint(handler: EntryPointHandler): this;
@@ -575,31 +304,47 @@ declare class Flare {
575
304
  private buildBaseAttributes;
576
305
  private assembleAttributes;
577
306
  private buildLogAttributes;
307
+ /**
308
+ * Local roots only, snapshotted by the Tracer at span START so a long-lived root does not drift into
309
+ * the next page's scope. Children get none, and no span ever runs the DOM collector.
310
+ *
311
+ * Everything assembled is inherited except user identity (excluding the opaque `user.id`): a root span
312
+ * goes out for every page view, so email, full name, IP and `user.attributes` would turn normal
313
+ * browsing into PII traffic. The rest — `context.custom`, `addContextGroup` bags — stays in, because
314
+ * the trace viewer renders any span attribute whose key does not start with `flare.`.
315
+ */
316
+ private getScopeAttributes;
317
+ private spanResourceAttributes;
578
318
  private buildReport;
579
319
  sendReport(report: Report): Promise<void>;
580
320
  }
581
321
  //#endregion
322
+ //#region src/breadcrumbs/recordBreadcrumb.d.ts
323
+ declare const MAX_BREADCRUMB_URL_LENGTH = 256;
324
+ declare function breadcrumbUrl(href: string, denylist: RegExp): string;
325
+ declare function recordBreadcrumb(scopeProvider: ScopeProvider, config: Config, type: string, attributes: Attributes, startTimeUnixNano: number): void;
326
+ //#endregion
327
+ //#region src/tracing/envelope.d.ts
328
+ declare function buildTracesEnvelope(spans: BufferedSpan[], resourceAttributes: Attributes, scopeName: string, scopeVersion: string): TracesEnvelope;
329
+ //#endregion
330
+ //#region src/tracing/traceparent.d.ts
331
+ declare function buildTraceparent(traceId: string, spanId: string, sampled: boolean): string;
332
+ declare function parseTraceparent(header: string): {
333
+ traceId: string;
334
+ parentSpanId: string;
335
+ sampled: boolean;
336
+ } | null;
337
+ //#endregion
338
+ //#region src/tracing/ids.d.ts
339
+ declare function spanId(): string;
340
+ //#endregion
582
341
  //#region src/stacktrace/NullFileReader.d.ts
583
342
  /**
584
- * No-op `FileReader` that returns `null` for every URL it is asked to read.
585
- *
586
- * Used as the default for `Flare`'s `fileReader` constructor parameter so the
587
- * class is usable without picking a side: instantiated bare (`new Flare()`),
588
- * reports still build, but stack frames omit source-code snippets — which is
589
- * the correct, safe behavior in an environment we know nothing about.
590
- *
591
- * The two real implementations live in the consumer packages and take their
592
- * place once the right environment is established:
593
- *
594
- * - `@flareapp/js` injects `FetchFileReader`, which `fetch()`s source maps
595
- * and original files over HTTP for browser stack frames.
596
- * - `@flareapp/node` injects `DiskFileReader`, which reads files from disk
597
- * via `node:fs/promises` for server stack frames.
598
- *
599
- * The interface (`read(url) -> Promise<string | null>`) lets the stack-trace
600
- * builder treat all three the same way: ask for a URL, render the snippet
601
- * when text comes back, gracefully skip it when `null` does. No environment
602
- * checks anywhere in core.
343
+ * No-op `FileReader` returning `null` for every URL. Default for `Flare`'s `fileReader` param so `new Flare()` builds
344
+ * reports without picking an environment; stack frames just omit source snippets. Consumer packages inject the real
345
+ * ones: `@flareapp/js` a fetch-based reader, `@flareapp/node` a disk reader. The `read(url) -> Promise<string | null>`
346
+ * interface lets the stack-trace builder treat all three the same (render on text, skip on null), so core needs no
347
+ * environment checks.
603
348
  */
604
349
  declare class NullFileReader implements FileReader {
605
350
  read(_url: string): Promise<string | null>;
@@ -608,4 +353,4 @@ declare class NullFileReader implements FileReader {
608
353
  //#region src/stacktrace/createStackTrace.d.ts
609
354
  declare function createStackTrace(error: Error, debug: boolean, fileReader: FileReader): Promise<Array<StackFrame>>;
610
355
  //#endregion
611
- export { type AnyValue, Api, type AttributeValue, type Attributes, type BufferedLog, type Config, type ContextCollector, DEFAULT_URL_DENYLIST, type EntryPointHandler, type FileReader, Flare, type FlushFn, type FlushScheduler, type Framework, GlobalScopeProvider, type Glow, type KeyValue, Logger, type LoggerDeps, type LogsEnvelope, type MessageLevel, NoopFlushScheduler, NullFileReader, type OtelLogRecord, type OverriddenGrouping, type RejectionReporter, type Report, Scope, type ScopeProvider, type SdkInfo, type SpanEvent, type StackFrame, USER_IDENTITY_KEYS, type User, assert, assertKey, convertToError, createStackTrace, describeRejectionReason, extractCode, flatJsonStringify, getCodeSnippet, glowsToEvents, now, readLinesFromFile, redactUrlQuery, resolveDenylist, routeRejection, userIdentityAttributes };
356
+ export { type ActiveSpanHolder, type AnyValue, Api, type AttributeValue, type Attributes, BrowserSpanEventType, BrowserSpanType, type BufferedLog, type BufferedSpan, type Config, type ContextCollector, DEFAULT_MAX_LIVE_TRACES, DEFAULT_URL_DENYLIST, type EntryPointHandler, type EntryPointType, type FileReader, Flare, type FlushFn, type FlushScheduler, type Framework, FrameworkName, GlobalScopeProvider, type Glow, InMemoryActiveSpanHolder, type KeyValue, Logger, type LoggerDeps, type LogsEnvelope, MAX_BREADCRUMB_URL_LENGTH, type MessageLevel, NoopFlushScheduler, NullFileReader, type OtelLogRecord, type OtelSpan, type OverriddenGrouping, type RejectionReporter, type Report, type SafeCloneOptions, type SamplingContext, Scope, type ScopeProvider, type SdkInfo, type SdkTaggable, type Span, type SpanEvent, type SpanLifecycleEvent, type SpanLifecycleListener, type SpanOptions, type SpanPhase, type SpanStatus, SpanStatusCode, type SpanTypeName, type StackFrame, Tracer, type TracerDeps, type TracesEnvelope, type TracesSampler, USER_IDENTITY_KEYS, type User, assert, assertKey, breadcrumbUrl, buildTraceparent, buildTracesEnvelope, convertToError, createIdentityTagger, createStackTrace, defaultNowNano, describeRejectionReason, extractCode, flatJsonStringify, getCodeSnippet, glowsToEvents, now, parseTraceparent, readLinesFromFile, recordBreadcrumb, redactObjectValues, redactUrlQuery, resolveDenylist, routeRejection, safeClone, safeDecode, spanId, toCustomContext, urlAttributes, userIdentityAttributes };