{"_id":"@almadar/analytics-server","name":"@almadar/analytics-server","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@almadar/analytics-server","version":"0.1.0","description":"Tiny cookieless analytics collector: sendBeacon ingest, daily-salt uniqueness, server-side GeoIP then IP discarded.","type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","bin":{"almadar-analytics-server":"dist/bin.js"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"}},"publishConfig":{"registry":"https://registry.npmjs.org","access":"public"},"optionalDependencies":{"maxmind":"^4.3.0"},"devDependencies":{"@almadar/eslint-plugin":">=2.7.0","@types/node":"^20.0.0","@typescript-eslint/parser":"8.56.0","eslint":"10.0.0","tsup":"^8.0.0","typescript":"^5.7.0","vitest":"^2.1.0"},"repository":{"type":"git","url":"git+https://github.com/almadar-io/almadar-analytics-server.git"},"license":"BSL-1.1","keywords":["almadar","analytics","cookieless","gdpr","privacy","collector"],"homepage":"https://github.com/almadar-io/almadar-analytics-server#readme","scripts":{"build":"tsup","build:watch":"tsup --watch","start":"node dist/bin.js","lint":"eslint src/","typecheck":"tsc --noEmit","test":"vitest --run"},"_id":"@almadar/analytics-server@0.1.0","bugs":{"url":"https://github.com/almadar-io/almadar-analytics-server/issues"},"_integrity":"sha512-E03NSEXV04Q5PKByfCY7IeN7kHfDLXIVCl7fpWaQlHgZaOh8k6gvDuyb41mFr2DRjzvFo+BXCjzelX2syg0i9w==","_resolved":"/tmp/953dc61747425d79442e670c4aa0a0a8/almadar-analytics-server-0.1.0.tgz","_from":"file:almadar-analytics-server-0.1.0.tgz","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-E03NSEXV04Q5PKByfCY7IeN7kHfDLXIVCl7fpWaQlHgZaOh8k6gvDuyb41mFr2DRjzvFo+BXCjzelX2syg0i9w==","shasum":"753190b54842092d1bccf7c6c395d319d286500b","tarball":"https://registry.npmjs.org/@almadar/analytics-server/-/analytics-server-0.1.0.tgz","fileCount":9,"unpackedSize":95522,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC/YvkC2F0SsVIUEvUJoHZxWLZEFjW77g3YEdylRhN+UwIgPfWIc7zDRy14VeHRog/JrlLZEmErTqM0t8mw5fv8uTo="}]},"_npmUser":{"name":"javasop","email":"osamah.net.m@gmail.com"},"directories":{},"maintainers":[{"name":"javasop","email":"osamah.net.m@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/analytics-server_0.1.0_1780494903463_0.6740995116345583"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-03T13:55:03.289Z","0.1.0":"2026-06-03T13:55:03.599Z","modified":"2026-06-03T13:55:03.840Z"},"maintainers":[{"name":"javasop","email":"osamah.net.m@gmail.com"}],"description":"Tiny cookieless analytics collector: sendBeacon ingest, daily-salt uniqueness, server-side GeoIP then IP discarded.","homepage":"https://github.com/almadar-io/almadar-analytics-server#readme","keywords":["almadar","analytics","cookieless","gdpr","privacy","collector"],"repository":{"type":"git","url":"git+https://github.com/almadar-io/almadar-analytics-server.git"},"bugs":{"url":"https://github.com/almadar-io/almadar-analytics-server/issues"},"license":"BSL-1.1","readme":"# @almadar/analytics-server\n\nA tiny cookieless analytics collector for [`@almadar/analytics`](../almadar-analytics).\nZero required dependencies — plain `node:http` + `node:crypto`, an append-only\nNDJSON store, and optional MaxMind GeoIP. Mountable as a standalone server or\ninto any Node HTTP framework.\n\n## How it stays GDPR-friendly\n\n- **No cookies, no identifiers leave the browser.** The client sends only the\n  event; the collector reads the request IP and User-Agent server-side.\n- **Daily-rotating salt.** Visitor uniqueness is `sha256(dailySalt + ip + ua +\n  domain)`. The salt lives in memory and rotates every UTC day, so a visitor\n  cannot be linked across days, and the hash is never reversible to an IP.\n- **IP is discarded.** It is used only to derive the daily hash and (optionally)\n  country/region, then it drops out of scope. It is never stored, logged, or\n  returned.\n- **Geography is coarse.** Country + region (ISO codes) only, from GeoLite2.\n\n## Run it\n\n```bash\nnpm i -g @almadar/analytics-server   # or run from the monorepo\nalmadar-analytics-server             # listens on :8787\n```\n\nConfiguration is via environment variables:\n\n| Var | Default | Description |\n|---|---|---|\n| `PORT` | `8787` | Listen port. |\n| `HOST` | all | Bind host. |\n| `ANALYTICS_DATA` | `./analytics.ndjson` | NDJSON store path. |\n| `ANALYTICS_GEOIP_DB` | _(off)_ | Path to a GeoLite2 `.mmdb`; enables geography. |\n| `ANALYTICS_TRUST_PROXY` | `true` | Read client IP from `X-Forwarded-For`. |\n| `ANALYTICS_ALLOW_ORIGIN` | `*` | `Access-Control-Allow-Origin`. |\n| `ANALYTICS_STATS_TOKEN` | _(off)_ | Bearer token for `GET /stats`; unset = disabled. |\n\nGeography needs a MaxMind GeoLite2 database (not redistributable): sign up for a\nfree MaxMind account, download `GeoLite2-City.mmdb`, point `ANALYTICS_GEOIP_DB`\nat it, and `npm i maxmind` (it is an optional dependency). Behind a CDN/proxy,\nforward the real visitor IP in `X-Forwarded-For` or geography and uniqueness\nbreak.\n\n## Endpoints\n\n- `POST /e` — ingest (`202`). Body is the beacon JSON.\n- `GET /health` — `200 ok`.\n- `GET /stats?domain=&from=&to=&limit=` — JSON aggregates. Requires\n  `Authorization: Bearer <ANALYTICS_STATS_TOKEN>`; disabled when no token is set.\n\n## Embed in an existing server\n\n```ts\nimport { createCollector, FileStore, createGeo } from '@almadar/analytics-server';\n\nconst collector = createCollector({\n  store: new FileStore('./analytics.ndjson'),\n  geo: await createGeo(process.env.ANALYTICS_GEOIP_DB),\n  statsToken: process.env.ANALYTICS_STATS_TOKEN,\n});\n// node:http\nhttp.createServer((req, res) => collector.handle(req, res));\n```\n\n## Storage\n\nThe bundled `FileStore` (NDJSON, loaded into memory on startup) is fine for low\n-to-moderate volume. For high write volume, implement the `Store` interface\n(`record` / `stats` / `prune`) over SQLite or Postgres and pass it instead — the\ncollector depends only on that interface. Honor data-minimization retention\n(CNIL caps audience-measurement data at ~25 months): run `store.prune(cutoff)`\non a schedule.\n","readmeFilename":"README.md","_rev":"1-d5876c256a0c23e89b0c390244e67909"}