{"_id":"@anvilnine/telemetry","name":"@anvilnine/telemetry","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@anvilnine/telemetry","version":"0.1.0","description":"Shared analytics and feature-flag wiring for Anvil Nine products.","license":"MIT","author":{"name":"Zonily Jame"},"repository":{"type":"git","url":"git+https://github.com/anvilnine/telemetry.git"},"publishConfig":{"access":"public"},"type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.json","build:browser":"esbuild src/browser.ts --bundle --format=esm --minify --outfile=browser/telemetry.js","test":"vitest run","type-check":"tsc -p tsconfig.json --noEmit"},"devDependencies":{"@types/node":"^22.0.0","esbuild":"^0.28.1","jsdom":"^30.0.1","typescript":"^5.6.0","vitest":"^3.2.7"},"engines":{"node":">=22"},"_id":"@anvilnine/telemetry@0.1.0","gitHead":"290f06bd00d89ebc06b0b50f5c0a68206b75670e","bugs":{"url":"https://github.com/anvilnine/telemetry/issues"},"homepage":"https://github.com/anvilnine/telemetry#readme","_nodeVersion":"24.4.1","_npmVersion":"11.4.2","dist":{"integrity":"sha512-tHzbTBqNzNH6N5cLSg+VGlyGMeQ6/OKkjcWgxO7o0XKihVHYhjs+JmXIwrOHtw0FSW5aAG4S1J8toLYA2CH4Jg==","shasum":"b26d79fa6f84d0c546f1d6d192a06d4179df8bc2","tarball":"https://registry.npmjs.org/@anvilnine/telemetry/-/telemetry-0.1.0.tgz","fileCount":15,"unpackedSize":32107,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCw35esmorqNtVtF71oKbvL2V+4DHg1mPVUS5iMbHhz/QIhAMsLvVCewVZX/o9e0cis7j9WB+x8jYAqdwpIWUavHLJU"}]},"_npmUser":{"name":"anvilnine-ops","email":"operations@anvilnine.com"},"directories":{},"maintainers":[{"name":"anvilnine-ops","email":"operations@anvilnine.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/telemetry_0.1.0_1785953494662_0.8936036823841997"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-05T18:11:34.522Z","0.1.0":"2026-08-05T18:11:34.810Z","modified":"2026-08-05T18:11:35.111Z"},"maintainers":[{"name":"anvilnine-ops","email":"operations@anvilnine.com"}],"description":"Shared analytics and feature-flag wiring for Anvil Nine products.","homepage":"https://github.com/anvilnine/telemetry#readme","repository":{"type":"git","url":"git+https://github.com/anvilnine/telemetry.git"},"author":{"name":"Zonily Jame"},"bugs":{"url":"https://github.com/anvilnine/telemetry/issues"},"license":"MIT","readme":"# @anvilnine/telemetry\n\nShared analytics and feature-flag wiring for Anvil Nine products.\n\nThis package holds the code no product had yet: the GrowthBook wrapper, the distinct id, and\nautotracking for clicks, forms, scroll depth and outbound links. It does **not** replace the\nworking Umami adapters in `namescout` and `mobrev`. Both keep theirs.\n\nThe contract every product follows lives in [`docs/contract.md`](docs/contract.md). Read that\nfirst. It is the part that has to stay identical across repos.\n\n## Why it takes a function instead of an interface\n\nEvery product already has an analytics client, and they disagree on the method name. Namescout\ncalls it `track`, mobrev calls it `capture`. Namescout's interface is also generic over its own\nevent map, so it cannot be shared without rewriting it.\n\nSo this package takes a plain function:\n\n```ts\ntype TrackFn = (event: string, props?: Record<string, unknown>) => void\n```\n\nEach app passes whatever its own client already does. Nothing needs a migration to start using\nthis, and the two working implementations stay untouched.\n\n## Install\n\n```bash\nnpm i @anvilnine/telemetry\n```\n\nNo runtime dependencies. GrowthBook is optional and stays the caller's dependency, because\n`growthbookConfig()` returns a config object rather than constructing a client.\n\n`dropkiln` serves its pages under a `script-src 'self'` policy, so its browser code cannot load a\nmodule from a CDN and it has no bundler to inline one. It vendors the prebuilt bundle from\n`npm run build:browser` instead. See `docs/contract.md`.\n\n## Use\n\n```ts\nimport {\n  createAutotrack,\n  createFlags,\n  growthbookConfig,\n  getDistinctId,\n  startUmami,\n  withAssignments,\n} from '@anvilnine/telemetry'\nimport { GrowthBook } from '@growthbook/growthbook'\n\n// 1. An analytics client. Products that already have one skip this and pass\n//    their own method as `track` below.\nconst umami = startUmami({\n  hostUrl: import.meta.env.PUBLIC_UMAMI_HOST,\n  websiteId: import.meta.env.PUBLIC_UMAMI_WEBSITE_ID,\n})\n\n// 2. Autotracking. Clicks, forms, scroll depth, outbound links.\nconst auto = createAutotrack({ track: umami.track })\nauto.start()\n\n// 3. Flags. GrowthBook assigns; the exposure event comes back through analytics.\nconst gb = new GrowthBook(\n  growthbookConfig({\n    apiHost: import.meta.env.PUBLIC_GROWTHBOOK_HOST,\n    clientKey: import.meta.env.PUBLIC_GROWTHBOOK_KEY,\n    track: umami.track,\n  }),\n)\nconst flags = createFlags({ client: gb, track: umami.track, holdToControl: true })\nawait flags.ready()\n\nif (flags.isOn('web-hero-headline')) {\n  // render the variation\n}\n\n// 4. Stamp the variation onto a conversion so Umami can segment it.\numami.track('form_submit', withAssignments(flags, { form: 'waitlist', ok: true }))\n```\n\nMarkup opts in with attributes. No per-element wiring:\n\n```html\n<button data-track-id=\"hero-primary\" data-track-location=\"hero\">Start</button>\n<form data-track-form=\"waitlist\">...</form>\n<a href=\"https://namescout.app\" data-track-product=\"namescout\" data-track-location=\"card\">NameScout</a>\n```\n\n## Everything is off by default\n\nWith a blank website id and a blank client key, nothing loads and no request is made. That is the\nstate in local development, in CI, and in any self-hosted install. Turning telemetry on is setting\ntwo environment variables, and turning it off is unsetting them.\n\nNothing here throws. A dead analytics host loses data and changes nothing else on the page.\n\n## Hold experiments to control\n\n`holdToControl: true` still logs `experiment_viewed`, but every flag returns its default. Use it\nuntil a surface clears roughly 200 sessions a week. It proves the wiring works while a split would\nstill be meaningless.\n\nThree of the four Anvil Nine surfaces are in that state today: anvilnine.com is noindexed,\nmobrev is pre-launch, and dropkiln.com does not exist yet.\n\n## Develop\n\n```bash\nnpm install\nnpm test          # vitest, jsdom\nnpm run type-check\nnpm run build     # tsc to dist/\n```\n\n`src/events.test.ts` asserts the exact five event names. That test is there to make a rename fail\nloudly rather than silently split a funnel's history in Umami.\n","readmeFilename":"README.md","_rev":"1-d592eb1e45c285d530cb28bde0b4a9cd"}