first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
# `sampling.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Implements head-based sampling for `@suaveplan/analytics-browser`. The tracker calls `sampler.shouldSample(event)` after the consent gate but before the scrubber so events that will be dropped never pay the cost of property scrubbing or queue persistence. `HeadSampler` accepts a base sample rate in `[0, 1]` plus an optional `Record<eventName, rate>` override map so common events (e.g. `page`) can be captured at a different rate than rare events (e.g. `error_reported`).
## Features
- Per-event-name overrides — `new HeadSampler(0.1, { error_reported: 1 })` keeps every error while sampling everything else at 10 %.
- Strict clamping — rates `<= 0` always drop, rates `>= 1` always keep; `NaN` clamps to 0.
- Injectable random source — the constructor accepts a `random: () => number` seam so tests can drive deterministic decisions without monkey-patching `Math.random`.
- Stateless — every call is independent; safe to share a sampler across many trackers.
## Usage: Basic
```ts
import { HeadSampler } from "@suaveplan/analytics-browser/sampling";
const sampler = new HeadSampler(0.25); // keep 25 % of events
```
## Usage: Advanced
```ts
import { HeadSampler } from "@suaveplan/analytics-browser/sampling";
// 1 % sampling for verbose page-view events, 100 % for clicks and errors.
const sampler = new HeadSampler(0.01, {
clicked: 1,
error_reported: 1,
});
// In tests, inject a deterministic counter so behaviour is reproducible.
let n = 0;
const deterministic = new HeadSampler(0.5, {}, () => {
const sequence = [0.4, 0.6, 0.2, 0.8];
return sequence[n++ % sequence.length] as number;
});
```
## API Reference
- `class HeadSampler implements Sampler`
- `new HeadSampler(rate, overrides?, random?)`
- `rate: number` — base rate in `[0, 1]`. Values outside this range clamp.
- `overrides?: Readonly<Record<string, number>>` — per-event-name rate. Each entry clamps independently.
- `random?: () => number` — random source; defaults to `Math.random`.
- `shouldSample(event): boolean` — `true` keeps the event, `false` drops it.
## Implementation Notes
The clamp logic guards against floating-point drift and pathological caller input (e.g. accidentally passing `rate=2`). NaN clamps to 0 rather than 1 so a misconfigured environment variable does not silently flood the backend. The injected random source enables deterministic testing without resorting to `vi.stubGlobal` (banned by §2 of `AGENTS.md`).