@flareapp/core 2.7.0 → 2.8.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.cts 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 User, A as BufferedSpan, B as OtelSpan, D as AttributeValue, E as AnyValue, F as Glow, G as Span, H as Report, I as KeyValue, J as SpanStatus, K as SpanEvent, L as LogsEnvelope, M as EntryPointHandler, N as EntryPointType, O as Attributes, P as Framework, Q as TracesSampler, R as MessageLevel, T as assert, U as SamplingContext, V as OverriddenGrouping, W as SdkInfo, X as StackFrame, Y as SpanStatusCode, Z as TracesEnvelope, _ as flatJsonStringify, a as SafeCloneOptions, b as createIdentityTagger, c as describeRejectionReason, d as redactObjectValues, et as BrowserSpanType, f as redactUrlQuery, g as glowsToEvents, h as now, j as Config, k as BufferedLog, l as routeRejection, m as safeDecode, n as urlAttributes, nt as FrameworkName, o as safeClone, p as resolveDenylist, q as SpanOptions, r as toCustomContext, s as RejectionReporter, tt as SpanTypeName, u as DEFAULT_URL_DENYLIST, v as extractCode, w as assertKey, x as convertToError, y as SdkTaggable, z as OtelLogRecord } from "./urlAttributes-CvOw6tU3.cjs";
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,46 @@ 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[];
321
85
  pendingAttributes: Attributes;
322
86
  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
- */
87
+ /** Caps at `maxGlowsPerReport` by dropping the oldest, so the payload stays bounded. */
332
88
  addGlow(glow: Glow, maxGlowsPerReport: number): void;
333
89
  clearGlows(): void;
334
- /**
335
- * Set a single attribute on this scope. Called from `Flare.addContext` and
336
- * `Flare.addContextGroup`. Last write wins.
337
- */
338
90
  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
- */
91
+ /** Shallow: last write wins per key, nested objects are not deep-merged. */
345
92
  mergeAttributes(partial: Attributes): void;
346
93
  }
347
94
  /**
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.
95
+ * The seam through which `Flare` reaches its current `Scope`; implementations decide what "current" means.
96
+ * `@flareapp/node`'s returns the per-request `NodeScope` from `node:async_hooks`, falling back to a shared
97
+ * scope outside any `runWithContext(...)`.
359
98
  */
360
99
  interface ScopeProvider {
361
100
  active(): Scope;
362
101
  }
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
- */
102
+ /** One `Scope` for the provider's lifetime. The right default for a browser tab or a CLI script. */
369
103
  declare class GlobalScopeProvider implements ScopeProvider {
370
104
  private scope;
371
105
  active(): Scope;
@@ -385,6 +119,102 @@ type ReaderResponse = {
385
119
  declare function getCodeSnippet(fileReader: FileReader, url?: string, lineNumber?: number, columnNumber?: number): Promise<ReaderResponse>;
386
120
  declare function readLinesFromFile(fileText: string, lineNumber: number, columnNumber?: number, maxSnippetLineLength?: number, maxSnippetLines?: number): ReaderResponse;
387
121
  //#endregion
122
+ //#region src/tracing/context.d.ts
123
+ interface ActiveSpanHolder {
124
+ getActive(): Span | undefined;
125
+ /**
126
+ * Run `fn` with `span` active, restoring the prior active span afterward. A callback (not a bare setter) so a Node
127
+ * holder can back it with AsyncLocalStorage.run(...) to preserve async-scoped context.
128
+ */
129
+ withActive<T>(span: Span, fn: () => T): T;
130
+ /**
131
+ * Persistent "active root" that getActive() falls back to when no withActive scope is on the stack. Used by
132
+ * long-lived pageload/navigation roots so child spans (e.g. fetches) auto-parent to them. Optional; a holder that
133
+ * omits it simply has no active-root support.
134
+ */
135
+ setActiveRoot?(span: Span | undefined): void;
136
+ }
137
+ declare class InMemoryActiveSpanHolder implements ActiveSpanHolder {
138
+ private active;
139
+ private root;
140
+ getActive(): Span | undefined;
141
+ withActive<T>(span: Span, fn: () => T): T;
142
+ setActiveRoot(span: Span | undefined): void;
143
+ }
144
+ //#endregion
145
+ //#region src/tracing/Tracer.d.ts
146
+ declare function defaultNowNano(): number;
147
+ type SpanPhase = 'start' | 'end';
148
+ type SpanLifecycleEvent = {
149
+ phase: SpanPhase;
150
+ span: Span;
151
+ };
152
+ type SpanLifecycleListener = (event: SpanLifecycleEvent) => void;
153
+ /** Bounded backstop for the live TraceState map: an app that never ends spans must not grow it forever. */
154
+ declare const DEFAULT_MAX_LIVE_TRACES = 1000;
155
+ type TracerDeps = {
156
+ api: Api;
157
+ getConfig: () => Config;
158
+ getSdkInfo: () => SdkInfo;
159
+ getFramework: () => Framework | null;
160
+ getScopeAttributes: () => Attributes;
161
+ getResourceAttributes: () => Attributes;
162
+ track: <T>(p: Promise<T>) => Promise<T>;
163
+ scheduler: FlushScheduler;
164
+ activeSpanHolder?: ActiveSpanHolder;
165
+ now?: () => number;
166
+ rng?: () => number;
167
+ maxLiveTraces?: number;
168
+ };
169
+ declare class Tracer {
170
+ private deps;
171
+ private buffer;
172
+ private holder;
173
+ private traceStates;
174
+ private closedTraces;
175
+ private stateGeneration;
176
+ private now;
177
+ private rng;
178
+ private maxLiveTraces;
179
+ private epoch;
180
+ private pendingContinuation;
181
+ private spanListeners;
182
+ constructor(deps: TracerDeps);
183
+ getActiveSpan(): Span | undefined;
184
+ setActiveRoot(span?: Span): void;
185
+ /**
186
+ * Take one span against `traceId`'s cap up front, for a caller that publishes a span id before the
187
+ * span exists (the component profilers do; their descendants record first). False means the trace is
188
+ * full and the caller should stay transparent instead of handing out an id the cap will refuse.
189
+ * Consumed by the matching `startSpan({ claimed: true })`.
190
+ */
191
+ claimSpanSlot(traceId: string): boolean;
192
+ addSpanListener(fn: SpanLifecycleListener): () => void;
193
+ private emitSpanEvent;
194
+ flush(opts?: {
195
+ keepalive?: boolean;
196
+ }): void;
197
+ clear(): void;
198
+ continueFromTraceparent(header: string): void;
199
+ /**
200
+ * Runs `fn` with the span active, so spans started inside auto-parent to it, then ends it.
201
+ * Records an error status first if `fn` throws or its returned promise rejects.
202
+ */
203
+ withSpan<T>(name: string, fn: (span: Span) => T, opts?: SpanOptions): T;
204
+ /** Starts a span the caller must end. Unlike `withSpan`, it does not become the active span,
205
+ * so spans started after it do not auto-parent to it. */
206
+ startSpan(name: string, opts?: SpanOptions): Span;
207
+ /** A real Span handle that records nothing, so callers never have to branch on whether tracing is on. */
208
+ private startInertSpan;
209
+ private resolveTrace;
210
+ private getOrSeedState;
211
+ private createState;
212
+ private makeSpan;
213
+ /** Bounded, LRU by insertion order, like traceStates. Holds primitives only, never a span. */
214
+ private rememberClosed;
215
+ private onSpanEnd;
216
+ }
217
+ //#endregion
388
218
  //#region src/Flare.d.ts
389
219
  type ContextCollector = (config: Readonly<Config>) => Attributes;
390
220
  declare class Flare {
@@ -394,157 +224,54 @@ declare class Flare {
394
224
  private scopeProvider;
395
225
  private inflight;
396
226
  private _logger;
227
+ private _tracer;
397
228
  private _config;
398
229
  private sdkInfo;
399
230
  private framework;
400
231
  /**
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.
232
+ * @param api fetch transport for reports, logs and traces. Stateless: ingest url and
233
+ * key are passed per call, so tests swap in a fake.
234
+ * @param contextCollector per-report attributes (browser DOM, Node process). No-op by default.
235
+ * @param fileReader source files for stack-trace snippets. Defaults to no snippets;
236
+ * `@flareapp/js` injects a fetch reader, `@flareapp/node` a disk reader.
237
+ * @param scopeProvider the current `Scope`. Browser uses one global scope; Node an
238
+ * AsyncLocalStorage-backed provider so each request gets its own.
239
+ * @param scheduler drains the log and span buffers when the host's lifecycle ends (browser
240
+ * unload, process exit). No-op by default, leaving only size/timer flushes.
241
+ * @param activeSpanHolder tracks the active span so new spans auto-parent to it. In-memory by
242
+ * default; a platform can back it with AsyncLocalStorage instead.
411
243
  */
412
- constructor(api?: Api, contextCollector?: ContextCollector, fileReader?: FileReader, scopeProvider?: ScopeProvider, scheduler?: FlushScheduler);
244
+ constructor(api?: Api, contextCollector?: ContextCollector, fileReader?: FileReader, scopeProvider?: ScopeProvider, scheduler?: FlushScheduler, activeSpanHolder?: ActiveSpanHolder);
413
245
  /**
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.
246
+ * Register an in-flight report so `flush()` can wait for it. Every entry point wraps its whole async
247
+ * pipeline, from beforeEvaluate through api.report, so flush() waits on all of it.
431
248
  *
432
- * So we build a SHADOW promise that mirrors `p`'s timing but cannot
433
- * reject:
434
- *
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.
249
+ * What goes in the Set is a shadow promise that mirrors `p`'s timing but cannot reject, so a failed
250
+ * report never surfaces as an unhandled rejection warning. `p` itself is returned untouched, so the
251
+ * caller still observes real success or failure.
463
252
  */
464
253
  private track;
465
254
  /**
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.
255
+ * Wait until every in-flight report settles or `timeoutMs` elapses. Always resolves, never rejects.
256
+ * Written for `@flareapp/node`'s fatal handler, which awaits the fatal report itself then flushes to
257
+ * drain any other concurrent reports before `process.exit`.
499
258
  *
500
- * return new Promise<void>((resolve) => {
501
- * const timer = setTimeout(resolve, timeoutMs);
502
- * Promise.allSettled(pending).then(() => {
503
- * clearTimeout(timer);
504
- * resolve();
505
- * });
506
- * });
507
- *
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.
259
+ * Snapshotting the Set bounds the wait: reports started after this line are not awaited, so a handler
260
+ * that keeps emitting during shutdown cannot block the process forever. Call flush again for those.
543
261
  */
544
262
  flush(timeoutMs?: number): Promise<void>;
545
263
  get config(): Readonly<Config>;
546
264
  get glows(): readonly Glow[];
547
265
  get logger(): Logger;
266
+ get tracer(): Tracer;
267
+ /** Starts a span the caller must end. Unlike `withSpan`, it does not become the active span,
268
+ * so spans started after it do not auto-parent to it. */
269
+ startSpan(name: string, opts?: SpanOptions): Span;
270
+ /**
271
+ * Runs `fn` with the span active, so spans started inside auto-parent to it, then ends it.
272
+ * Records an error status first if `fn` throws or its returned promise rejects.
273
+ */
274
+ withSpan<T>(name: string, fn: (span: Span) => T, opts?: SpanOptions): T;
548
275
  light(key?: string, debug?: boolean): this;
549
276
  configure(config: Partial<Config>): this;
550
277
  test(): Promise<void>;
@@ -554,11 +281,8 @@ declare class Flare {
554
281
  addContext(name: string, value: AttributeValue): this;
555
282
  addContextGroup(groupName: string, value: Record<string, AttributeValue>): this;
556
283
  /**
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.
284
+ * Maps the known fields onto the keys the Flare backend reads (see `USER_FIELD_KEYS`) and bundles
285
+ * anything else into `user.attributes`. Pass `null` to clear. In Node this targets the per-request scope.
562
286
  */
563
287
  setUser(user: User | null): this;
564
288
  setEntryPoint(handler: EntryPointHandler): this;
@@ -575,31 +299,42 @@ declare class Flare {
575
299
  private buildBaseAttributes;
576
300
  private assembleAttributes;
577
301
  private buildLogAttributes;
302
+ /**
303
+ * Local roots only, snapshotted by the Tracer at span START so a long-lived root does not drift into
304
+ * the next page's scope. Children get none, and no span ever runs the DOM collector.
305
+ *
306
+ * Everything assembled is inherited except user identity (excluding the opaque `user.id`): a root span
307
+ * goes out for every page view, so email, full name, IP and `user.attributes` would turn normal
308
+ * browsing into PII traffic. The rest — `context.custom`, `addContextGroup` bags — stays in, because
309
+ * the trace viewer renders any span attribute whose key does not start with `flare.`.
310
+ */
311
+ private getScopeAttributes;
312
+ private spanResourceAttributes;
578
313
  private buildReport;
579
314
  sendReport(report: Report): Promise<void>;
580
315
  }
581
316
  //#endregion
317
+ //#region src/tracing/envelope.d.ts
318
+ declare function buildTracesEnvelope(spans: BufferedSpan[], resourceAttributes: Attributes, scopeName: string, scopeVersion: string): TracesEnvelope;
319
+ //#endregion
320
+ //#region src/tracing/traceparent.d.ts
321
+ declare function buildTraceparent(traceId: string, spanId: string, sampled: boolean): string;
322
+ declare function parseTraceparent(header: string): {
323
+ traceId: string;
324
+ parentSpanId: string;
325
+ sampled: boolean;
326
+ } | null;
327
+ //#endregion
328
+ //#region src/tracing/ids.d.ts
329
+ declare function spanId(): string;
330
+ //#endregion
582
331
  //#region src/stacktrace/NullFileReader.d.ts
583
332
  /**
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.
333
+ * No-op `FileReader` returning `null` for every URL. Default for `Flare`'s `fileReader` param so `new Flare()` builds
334
+ * reports without picking an environment; stack frames just omit source snippets. Consumer packages inject the real
335
+ * ones: `@flareapp/js` a fetch-based reader, `@flareapp/node` a disk reader. The `read(url) -> Promise<string | null>`
336
+ * interface lets the stack-trace builder treat all three the same (render on text, skip on null), so core needs no
337
+ * environment checks.
603
338
  */
604
339
  declare class NullFileReader implements FileReader {
605
340
  read(_url: string): Promise<string | null>;
@@ -608,4 +343,4 @@ declare class NullFileReader implements FileReader {
608
343
  //#region src/stacktrace/createStackTrace.d.ts
609
344
  declare function createStackTrace(error: Error, debug: boolean, fileReader: FileReader): Promise<Array<StackFrame>>;
610
345
  //#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 };
346
+ export { type ActiveSpanHolder, type AnyValue, Api, type AttributeValue, type Attributes, 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, 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, buildTraceparent, buildTracesEnvelope, convertToError, createIdentityTagger, createStackTrace, defaultNowNano, describeRejectionReason, extractCode, flatJsonStringify, getCodeSnippet, glowsToEvents, now, parseTraceparent, readLinesFromFile, redactObjectValues, redactUrlQuery, resolveDenylist, routeRejection, safeClone, safeDecode, spanId, toCustomContext, urlAttributes, userIdentityAttributes };