/** * Encode-side byte I/O: a growable `Writer`, the interning `StringTable` (build side), the * `FieldEncoder` the encode walker builds on, and the JsonValue encoder. Everything here is * hand-rolled and zero-dependency on purpose — this is the path that runs on a real user's page * (AGENTS "tiny on the page"), so it must be small, tree-shakeable, and free of any runtime import. * The decode mirror of every primitive lives in `field-decoder.ts`; the round-trip corpus proves the * two are exact inverses. * * (`FieldEncoder` was named `Encoder` before the encode/decode split; it was renamed to free `Encoder` * for the public streaming class in `../encoder.ts`.) */ import type { RelMs, DurationMs } from '../time.js'; import type { JsonValue } from '../json.js'; /** * Growable little-endian byte sink. Capacity doubles on demand; `finish()` returns a view of exactly * the written bytes (no copy). Numbers are written as LEB128 varints (the small non-negative integers * that dominate: counts, ids, sizes, string refs), zigzag varints (the fixed-point µs timeline ticks — * see `FieldEncoder.rel`/`dur`), or 8-byte IEEE-754 doubles (wall-clock `EpochMs` plus true floats — * rects, ratios, CLS value — that can be fractional or negative and are not on the µs timeline grid). */ export declare class Writer { private buf; private view; private pos; constructor(initialCapacity?: number); private ensure; u8(b: number): void; /** * Unsigned LEB128. Uses `% 128` / `Math.floor(n / 128)` rather than `& 0x7f` / `>>> 7` so values * above 2^32 (e.g. large `decodedBodySize`) survive — JS bitwise ops truncate to 32 bits and would * corrupt them silently. Non-integer or negative input is a programming error (a field was routed * to the wrong writer); we throw loudly rather than encode a wrong value. */ varuint(n: number): void; /** * Signed LEB128 via zigzag (…, -2→3, -1→1, 0→0, 1→2, 2→4, …). Uses `* 2` / `* -2 - 1` rather than * `<< 1` / `>> 31` so it stays correct past 2^32 — a microsecond timestamp for a multi-minute * session already exceeds 2^31. Small magnitudes (the common case after delta-encoding) stay 1 byte. */ zigzag(n: number): void; /** 8-byte IEEE-754 double, little-endian. Lossless for every JS number, including NaN and -0. */ f64(x: number): void; bytes(b: Uint8Array): void; finish(): Uint8Array; } /** * String interning (build side). URLs, resource names, selectors, custom-event names/namespaces, and * the format's many small enum literals repeat heavily across a capture; storing each once and * referring to it by a varint id is the codec's single biggest size lever. Ids are assigned in * first-seen order during the encode pass, so the table can only be serialized once that pass is * complete — which is why the string-table section is emitted last but placed first in the file (see * pack.ts). The empty string is a normal, distinct value (e.g. `deliveryType: ''`) and interns like * any other; "absent" is carried by presence bits, never by the table. The read side is the free * function `decodeStringTable` in `field-decoder.ts`. * * Unicode carve-out (documented contract, see FileFormat.md): strings are stored as well-formed UTF-8 * via `TextEncoder`, which replaces LONE SURROGATES with U+FFFD — the same USVString normalization the * platform applies to URLs, `fetch`, and `postMessage`. A DOMString-sourced value that carries an * unpaired surrogate (e.g. a mark name truncated mid-emoji) therefore decodes as its U+FFFD-normalized * form; this is the ONE way `unpack(pack(c))` may differ from `c` besides µs quantization. */ export declare class StringTable { private readonly index; private readonly list; intern(s: string): number; encode(w: Writer): void; } /** ms → integer-µs ticks. Exported so the columnar slice encoder shares the ONE grid/rounding rule. */ export declare const toTicks: (ms: number) => number; /** * FieldEncoder = a byte `Writer` plus the capture-wide `StringTable`. One is created per section * (manifest, each stream, overhead, metadata) but they all share the same table, so a URL seen in * `resources` and again in `lcp` is stored once. This per-section split is also the incremental seam: * a driver can finalize each stream's bytes as that stream completes and flush only the (cheap) string * table at pagehide — never a big synchronous pack at unload (AGENTS "no unload cliff"). * * Codec v3 tick rules (both lossless; the corpus round-trip proves the mirrors): * - Every µs tick is divided by the capture-wide `scale` (the GCD of all ticks, from `GcdProbe`; * see pack.ts). Chrome captures sit on 1/5/100µs grids per capture; coarse-clock browsers coarsen * more. Division is exact by GCD construction. * - `R` (RelMs) values are CHAINED: each is written as the zigzag delta from the previous R value in * the same encoder scope (a section, a manifest-record tail, or a columnar column). Timeline points * within a scope are near-sorted, so deltas are small. Durations are magnitudes, not points — they * stay absolute (they're already small). */ export declare class FieldEncoder { readonly strings: StringTable; readonly w: Writer; /** µs-tick divisor (capture-wide GCD). pack() sets it after the probe pass; 1 = no scaling. */ scale: number; /** Chain state: the previous R tick written in THIS encoder's scope. */ private lastTick; constructor(strings: StringTable); u8(b: number): void; varuint(n: number): void; zigzag(n: number): void; f64(x: number): void; /** A point on the page timeline (RelMs): scaled integer-µs ticks, zigzag delta from the previous R. */ rel(x: RelMs): void; /** A duration (DurationMs): scaled integer-µs ticks, absolute (not chained). */ dur(x: DurationMs): void; /** A non-negative µs tick delta (the columnar profile-slice start column), scaled. */ tickDelta(delta: number): void; /** Swap the R-chain state (columnar columns are independent delta sequences). Returns the previous * tick so the caller can restore the enclosing scope's chain afterwards. */ chainSwap(tick?: number): number; /** A fresh sub-encoder sharing the table and scale, with its own chain scope — for length-prefixed * tails, which must decode standalone (the decoder mirrors with its own sub-decoder). */ sub(): FieldEncoder; bool(b: boolean): void; /** Intern `s` and write its varint id. All string-typed fields, including enum literals, go here. */ str(s: string): void; strArray(arr: readonly string[]): void; /** * Write a presence bitmap for a struct's optional fields, in declared order. Absent optionals then * cost one bit and nothing else — the "truly optional on the wire" tenet. A byte array (not a 32-bit * int) backs it so structs with >32 optionals (ResourceTiming has ~28, Navigation more) are fine. */ presence(flags: readonly boolean[]): void; } /** * Pass-1 probe for the capture-wide tick scale: the SAME walker runs over it (so it sees exactly the * ticks the real encode will write — a missed path is structurally impossible), but every byte write is * a no-op and only the GCD of |ticks| accumulates. Interning is skipped too: the real pass interns * first-seen along the identical walk, so ids don't depend on the probe. Cost is one extra model * traversal with no allocation — cheap next to gzip, and still O(n) (AGENTS "no unload cliff"). */ export declare class GcdProbe extends FieldEncoder { gcd: number; private acc; u8(): void; varuint(): void; zigzag(): void; f64(): void; bool(): void; str(): void; presence(): void; rel(x: RelMs): void; dur(x: DurationMs): void; tickDelta(delta: number): void; /** Length-prefixed tails share this probe: lengths don't matter in pass 1, ticks do. */ sub(): FieldEncoder; } /** * `JsonValue` encoder for User Timing `detail`, custom-event `details`, and capture `metadata`. Numbers * are stored as f64 (JSON numbers are doubles; this is lossless and payloads are small, so a tighter * int path isn't worth the branch). Keys are interned like any other string. Mirror: `decodeJson`. * * Input is normalized with **`JSON.stringify` semantics** (documented contract — see `json.ts` and * FileFormat.md): `toJSON()` is honored (so a `Date` encodes as its ISO string), `undefined`/function/ * symbol object properties are skipped, those values become `null` in arrays (and at the top level), * and non-finite numbers become `null`. This makes the encoder TOTAL — captures really do carry * `{ detail: { x: undefined } }` (structured clone preserves it) and that must never crash `pack()`. * Deviation from `JSON.stringify` (which throws): `bigint` encodes as `null`. Values that survive * normalization round-trip exactly. */ export declare function encodeJson(e: FieldEncoder, v: JsonValue): void; //# sourceMappingURL=field-encoder.d.ts.map