/** * The streaming `Encoder` — the library's headline surface. A caller brings their OWN capture code * (PerformanceObservers, the JS Self-Profiler, app instrumentation), constructs one Encoder, streams * events into it as they observe them, and calls `finish()` to get the packed `.rcap` bytes. Custom * events are authored with a stack-based context (`timeline().begin/end` or scoped `span()`) that * derives nesting depth from the call stack and duration from the begin→end delta. * * This lives on the ENCODE side (re-exported from `rumcap/encode` and `rumcap`, never `rumcap/decode`), * so it inherits the "tiny on the page" discipline: it accumulates the plain `Capture` model and folds * the profiler incrementally via the existing `SliceBuilder`, then delegates to `pack()`. Interning and * compression happen once, in `pack`, at `finish()` — the per-section seam for a future incremental * on-page driver already exists in the codec, but is not wired here yet. * * Note: `Encoder` is the public streaming class; the codec's low-level byte encoder is `FieldEncoder`. */ import { type StreamId, type StreamStatus } from './registry.js'; import { type RelMs, type EpochMs } from './time.js'; import type { JsonValue } from './json.js'; import type { Capture, OverheadReport } from './capture.js'; import type { CaptureConfig } from './config.js'; import type { Provenance, LossNote } from './manifest.js'; import type { NavigationTimingEntry, ResourceTimingEntry, PaintStream, LcpStream, ElementTimingEntry, LayoutShiftEntry, InteractionEntry, LongTaskEntry, LoafEntry, MarkEntry, MeasureEntry, VisibilityStateEntry, ErrorEntry, EnvironmentStream, CustomEvent, CustomEventTrack } from './streams/index.js'; import type { ProfilerTrace } from './streams/profile.js'; /** Construction options for {@link Encoder}. */ export interface EncoderInit { /** Capture-config that SHOULD have been attempted (also travels in the manifest). Defaults to `{ version: 1 }`. */ config?: CaptureConfig; /** The page's `performance.timeOrigin` (epoch anchor). Defaults to `performance.timeOrigin`. */ timeOrigin?: EpochMs; /** Monotonic offset of capture start from `timeOrigin`. Defaults to 0. */ captureStart?: RelMs; /** Reported timer precision/coarsening in ms, if known. */ precision?: number; /** The actual (clamped) profiler sample interval in ms, so the slice fold's floor/gap work from * chunk 1. A plain number: it is capture config, not a timeline value, so it carries no brand * (it can also be supplied per chunk — see `addProfilerChunk`). */ sampleIntervalMs?: number; /** Capture-level metadata (arbitrary JSON). May also be set later via `setMetadata`/`putMetadata`. * Copied shallowly on construction — later `putMetadata` calls never mutate the object you pass. */ metadata?: Record; /** Clock function returning the current page-relative time (RelMs). Defaults to `performance.now()`. * Inject a deterministic clock in tests. */ now?: () => RelMs; } /** * A handle to an open span. Hold it and call `.end()` when the work completes; nesting depth was * fixed at `begin()` time from the stack. Handle-based (not strictly LIFO), so overlapping/async spans * still close correctly — though strictly-nested usage is what yields clean 0/1/2… depths. */ export declare class Span { #private; private readonly track; /** @internal */ readonly _name: string; /** @internal */ readonly _start: RelMs; /** @internal */ readonly _depth: number; /** @internal */ readonly _details: JsonValue | undefined; constructor(track: Timeline, /** @internal */ _name: string, /** @internal */ _start: RelMs, /** @internal */ _depth: number, /** @internal */ _details: JsonValue | undefined); /** Close the span: records duration = now − start and emits the event. Optional end-time details are * merged over the begin-time details. Double-`end()` is a no-op. */ end(details?: JsonValue): void; } /** * One namespaced timeline (track). `begin`/`end`/`span` push and pop an internal stack so `depth` comes * from the call structure, not the caller. Obtain one via `encoder.timeline(namespace)` — repeated calls * for the same namespace return the same Timeline, so an app's spans stay on one track. * * A Timeline is a feed path like any Encoder method: once the Encoder has finished, `begin`/`span`/ * `instant`/`event` throw — a held handle can never silently mutate a finalized capture. */ export declare class Timeline { #private; readonly namespace: string; private readonly now; private readonly bump; private readonly assertOpen; constructor(namespace: string, now: () => RelMs, bump: (t: number) => void, assertOpen: () => void); /** Open a span at the current time; `depth` = the current open-span count. Returns a handle. */ begin(name: string, details?: JsonValue): Span; /** Scoped span: begin, run `fn` (awaiting if it returns a promise), end in `finally`; returns fn's * result. The ergonomic for "a stack of function calls building on an event context". */ span(name: string, fn: () => T): T; span(name: string, details: JsonValue, fn: () => T): T; /** A zero-duration event (a marker) at the current time and nesting depth. */ instant(name: string, details?: JsonValue): void; /** Low-level escape hatch: append a pre-measured event verbatim (start/duration/depth/details as given). */ event(ev: CustomEvent): void; /** @internal — called by `Span.end`. (Post-finish calls can't reach here: `_finalize` force-ends every * open span, so a held Span's `end()` is a documented double-end no-op by then.) */ _close(span: Span, endDetails?: JsonValue): void; /** @internal — end any still-open spans (at the current time) and return the finished track. */ _finalize(): CustomEventTrack; } /** * Stream a capture in, then `finish()` for the packed bytes. All feed methods return `this` for * chaining. Untouched streams are marked `not-requested`; a fed stream is `present`; use `markStream` * to record why one is absent (`unsupported`/`dropped`/`policy-blocked`). */ export declare class Encoder { #private; constructor(init?: EncoderInit); setNavigation(nav: NavigationTimingEntry): this; setPaint(paint: PaintStream): this; setLcp(lcp: LcpStream): this; setEnvironment(env: EnvironmentStream): this; addResource(r: ResourceTimingEntry): this; addLayoutShift(s: LayoutShiftEntry): this; addInteraction(e: InteractionEntry): this; addLongTask(t: LongTaskEntry): this; addLoaf(f: LoafEntry): this; addElementTiming(e: ElementTimingEntry): this; addVisibility(s: VisibilityStateEntry): this; addError(e: ErrorEntry): this; mark(m: MarkEntry): this; measure(m: MeasureEntry): this; /** Feed one raw `Profiler.stop()` chunk; folded into the accumulating call-tree immediately. * `sampleIntervalMs` is the ACTUAL (clamped) interval — pass `profiler.sampleInterval` here rather * than mutating the browser's trace. Adopted once (first source wins across this param, the * constructor option, then the trace's own field); the fold's floor/gap are derived from it. */ addProfilerChunk(trace: ProfilerTrace, sampleIntervalMs?: number): this; /** Get (or create) the timeline for `namespace`. Spans opened on it derive depth + duration. */ timeline(namespace: string): Timeline; /** Replace the capture-level metadata. Copied shallowly — the caller's object is never mutated. */ setMetadata(meta: Record): this; putMetadata(key: string, value: JsonValue): this; setOverhead(o: OverheadReport): this; /** Record an explicit per-stream manifest status — e.g. why a stream is absent * (`unsupported`/`not-requested`/`dropped`/`policy-blocked`), or present-with-provenance/loss. */ markStream(id: StreamId, status: StreamStatus, extra?: { loss?: LossNote[]; provenance?: Provenance; }): this; /** The assembled in-memory model (also what `finish()` packs). Finalizes the profiler fold + open * spans on first call; idempotent thereafter. */ toCapture(): Capture; /** Fold the profiler tail, assemble the TOTAL manifest, and pack. Async only because gzip is. * The result is cached: the double-save paths real pages have (`pagehide` AND * `visibilitychange`→hidden both firing) get the same bytes without re-packing. */ finish(): Promise; /** True once `finish()`/`toCapture()` has finalized the capture — further feeds would throw. * Feed paths that can fire late (a lingering PerformanceObserver delivery) check this to drop * silently instead; see `entrySink`. */ get finished(): boolean; } //# sourceMappingURL=encoder.d.ts.map