Consent-aware, deterministic analytics routing for TypeScript, Node.js, and browsers.
FlexTrack keeps product code independent from analytics vendors. Events are enriched, routed by explicit rules, checked for consent, sampled deterministically, delivered to registered trackers, and durably queued when the application is offline or a destination fails.
npm install flex-trackimport {
CallbackTracker,
ConsoleLogger,
ConsentManager,
configureFlexTrack,
} from "flex-track";
import { IndexedDbEventQueue } from "flex-track/browser";
const consent = new ConsentManager();
const flexTrack = configureFlexTrack((config) => {
config
.tracker(new CallbackTracker("analytics", async (event) => {
await analyticsClient.capture(event.name, event.properties);
}))
.tracker(new CallbackTracker("archive", sendToArchive))
.queue(new IndexedDbEventQueue())
.consent(() => consent.state)
.online(() => navigator.onLine)
.logger(new ConsoleLogger(import.meta.env.DEV));
config.routing((routing) => {
routing
.defineGroup("product", "analytics", "archive")
.routeCategory("business", (rule) =>
rule.toGroup("product").priority(10).requiresConsent(),
)
.routePII((rule) =>
rule.to("archive").priority(20).requiresPIIConsent(),
)
.routeHighVolume((rule) =>
rule.to("analytics").sample(0.1).priority(5),
)
.routeDefault((rule) => rule.to("analytics"));
});
});
await flexTrack.start();
consent.update({ general: true });
await flexTrack.track({
name: "add_to_cart",
category: "business",
properties: { product_id: "sku-42", price: 19.99 },
userId: "user-123",
});Consent defaults to denied. Grant it only after your application has collected the corresponding user choice.
Browser applications can use IndexedDB or localStorage:
import { IndexedDbEventQueue, LocalStorageEventQueue } from "flex-track/browser";Node.js applications can use an atomic JSON file queue:
import { FileEventQueue } from "flex-track/node";
config.queue(new FileEventQueue("./data/flex-track-queue.json"));When offline, FlexTrack queues one event with all pending tracker IDs. On partial failure, it stores only failed destinations. flush() retries pending destinations in FIFO order and preserves failed items with an incremented attempt count.
window.addEventListener("online", () => void flexTrack.flush());CallbackTracker integrates an existing SDK. FetchTracker posts the normalized event as JSON. DebugTracker captures events for development and tests.
import { FetchTracker } from "flex-track";
config.tracker(new FetchTracker("warehouse", "https://events.example.com/v1/track", {
headers: { authorization: `Bearer ${token}` },
}));Logging is opt-in. The default logger is a no-op. ConsoleLogger(false) reports routing decisions and property keys without values. Enable values only in trusted development environments:
config.logger(new ConsoleLogger(process.env.NODE_ENV !== "production"));- Node.js 18+
- Modern browsers with ESM; IndexedDB is optional
- ESM and CommonJS consumers
- TypeScript declarations included
The SDK implements the same core/runtime contract used by the Flutter, Kotlin, and Swift SDKs: priority-tier routing, UTF-8 FNV-1a sampling, consent defaults, immutable event identity, idempotent FIFO queues, offline delivery, partial-failure retry, and idempotent lifecycle behavior.
npm run typecheck
npm test
npm run build
npm run pack:checkSee CONTRIBUTING.md. Security issues should follow SECURITY.md.
MIT © Reza Taghizadeh