{"_id":"@agent-analytics/core","_rev":"4-19e90abf888c165082217befb1373c53","name":"@agent-analytics/core","dist-tags":{"latest":"0.2.2"},"versions":{"0.1.0":{"name":"@agent-analytics/core","version":"0.1.0","_id":"@agent-analytics/core@0.1.0","maintainers":[{"name":"dannyshmueli","email":"dannyshmueli@gmail.com"}],"dist":{"shasum":"284512356a993ae881a1686bf5b5835f21ab5a01","tarball":"https://registry.npmjs.org/@agent-analytics/core/-/core-0.1.0.tgz","fileCount":10,"integrity":"sha512-TVmu+N/pUEscq/elCeY7CiX5dqHo/JB2kLuZYOg4iJeotoy2H7B/5su/LnFKMLbsXPzQjNNopnVPM9jbFIWlww==","signatures":[{"sig":"MEQCIHU9gdjESap2XEUcy4LfOR0o3W5UL8kAgfzU7Ed2utdtAiBNvPi346UZvDWWcsw9pUFrthpfoNiR0abmu4sLq45t8g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":54064},"main":"src/index.js","type":"module","exports":{".":"./src/index.js","./ulid":"./src/ulid.js"},"gitHead":"4427b3fb6c85d0165735d12bf155a5e1e10f8e81","_npmUser":{"name":"dannyshmueli","email":"dannyshmueli@gmail.com"},"_npmVersion":"10.9.4","directories":{},"_nodeVersion":"22.22.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/core_0.1.0_1770662675134_0.9686687034865096","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@agent-analytics/core","version":"0.1.1","_id":"@agent-analytics/core@0.1.1","maintainers":[{"name":"dannyshmueli","email":"dannyshmueli@gmail.com"}],"dist":{"shasum":"189ccfdeb016b86830b436c152d83f50a3e9dd03","tarball":"https://registry.npmjs.org/@agent-analytics/core/-/core-0.1.1.tgz","fileCount":15,"integrity":"sha512-30vwKhMBscmxU1gmhulACgQJbfhknxT04XB0Sj073X1RofScbPt1slukVg6772XjZhWmPj2xSQKe+ckil9iwhg==","signatures":[{"sig":"MEUCICZs3E8UEL6+L6odRt/1s9tfgig0eFXC67HCQEzJWjFTAiEApfw8u02umzTAYO/NjGUbf8C6haHQf+hf3BFNY8soQXo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":83863},"main":"src/index.js","type":"module","exports":{".":"./src/index.js","./ulid":"./src/ulid.js"},"gitHead":"ccd119466a396d2171efb774d882bf503176bb2d","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"dannyshmueli","email":"dannyshmueli@gmail.com"},"_npmVersion":"11.5.1","description":"Platform-agnostic analytics engine. Zero dependencies — uses only Web APIs. Plug in your own database and auth to get a full analytics API on any runtime (Cloudflare Workers, Node.js, Deno, Bun, etc).","directories":{},"_nodeVersion":"24.7.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/core_0.1.1_1770759616747_0.4129628041711453","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@agent-analytics/core","version":"0.2.2","type":"module","main":"src/index.js","scripts":{"build":"node scripts/build-tracker.mjs","lint":"eslint .","test":"node --test test/*.test.mjs"},"exports":{".":"./src/index.js","./ulid":"./src/ulid.js","./base-adapter":"./src/db/base-adapter.js"},"devDependencies":{"@eslint/js":"^9.39.1","better-sqlite3":"^12.6.2","esbuild":"^0.27.3","eslint":"^9.39.1","globals":"^16.5.0"},"_id":"@agent-analytics/core@0.2.2","gitHead":"af823462ef8188b59a322a38b66334aa10bb8414","description":"Analytics engine with zero dependencies. Bring your own database and auth, get a full analytics API that runs anywhere (Workers, Node, Deno, Bun).","_nodeVersion":"24.7.0","_npmVersion":"11.5.1","dist":{"integrity":"sha512-pmZZx8POcgz7dhI09T7pbWhxkwFU9doZMTSxWhk3+M7SK2m58P6463+XlAc3u07JrXnPoYNlJEE/TeaR7yCBDQ==","shasum":"bfbdbd9a38bde57dd247fd5d4b18a4d8031cc4c8","tarball":"https://registry.npmjs.org/@agent-analytics/core/-/core-0.2.2.tgz","fileCount":53,"unpackedSize":408762,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC6k3WgsoavpA8dBnPW6Ia/3fVBPhCqBbPw/BrXjELgjAIhAJgTwWbb63kngzoVtFYTOfpBxMJsgFZc8whrNmIis3Ed"}]},"_npmUser":{"name":"dannyshmueli","email":"dannyshmueli@gmail.com"},"directories":{},"maintainers":[{"name":"support-agentanalytics.sh","email":"support@agentanalytics.sh"},{"name":"dannyshmueli","email":"dannyshmueli@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/core_0.2.2_1775942322225_0.42999150718662227"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-09T18:44:34.996Z","modified":"2026-04-11T21:18:42.598Z","0.1.0":"2026-02-09T18:44:35.268Z","0.1.1":"2026-02-10T21:40:16.919Z","0.2.2":"2026-04-11T21:18:42.453Z"},"description":"Analytics engine with zero dependencies. Bring your own database and auth, get a full analytics API that runs anywhere (Workers, Node, Deno, Bun).","maintainers":[{"name":"support-agentanalytics.sh","email":"support@agentanalytics.sh"},{"name":"dannyshmueli","email":"dannyshmueli@gmail.com"}],"readme":"# @agent-analytics/core\n\nAnalytics engine with zero dependencies. Bring your own database and auth, get a full analytics API that runs anywhere (Workers, Node, Deno, Bun).\n\n```bash\nnpm install @agent-analytics/core\n```\n\n## How it works\n\nYou give `createAnalyticsHandler` a database adapter and two auth functions. It gives you back a request handler.\n\n```js\nimport { createAnalyticsHandler, D1Adapter } from '@agent-analytics/core';\n\nconst handle = createAnalyticsHandler({\n  db: new D1Adapter(env.DB),\n  validateWrite: (request, body) => {\n    // check body.token for ingestion endpoints\n    return { valid: true };\n  },\n  validateRead: (request, url) => {\n    // check X-API-Key header for query endpoints\n    return { valid: true };\n  },\n});\n\nconst { response, writeOps } = await handle(request);\n// writeOps are DB write promises — pass them to ctx.waitUntil() on Workers\n```\n\nThe handler returns a standard `Response`. Write operations are deferred so you can `waitUntil` them on Workers or just `await` them on Node. Set `useQueue: true` to get `queueMessages` instead of `writeOps` if you want to push writes to a queue.\n\nInitialize your database with the included `schema.sql`.\n\n## Cloudflare Workers\n\n```js\nimport { createAnalyticsHandler, D1Adapter } from '@agent-analytics/core';\n\nexport default {\n  async fetch(request, env, ctx) {\n    const handle = createAnalyticsHandler({\n      db: new D1Adapter(env.DB),\n      validateWrite: (_request, body) => {\n        const token = body?.token;\n        if (!env.PROJECT_TOKENS) return { valid: true };\n        if (!token || !env.PROJECT_TOKENS.split(',').includes(token))\n          return { valid: false, error: 'invalid token' };\n        return { valid: true };\n      },\n      validateRead: (request, url) => {\n        const key = request.headers.get('X-API-Key') || url.searchParams.get('key');\n        if (!env.API_KEYS || !key || !env.API_KEYS.split(',').includes(key))\n          return { valid: false };\n        return { valid: true };\n      },\n    });\n\n    const { response, writeOps } = await handle(request);\n    if (writeOps) writeOps.forEach(op => ctx.waitUntil(op));\n    return response;\n  },\n};\n```\n\n## Client-side tracking\n\n```html\n<script defer src=\"https://your-server.com/tracker.js\" data-project=\"my-site\" data-token=\"YOUR_TOKEN\"></script>\n```\n\nAuto-tracks page views (including SPA navigations via patched `pushState`/`replaceState`), with URL, referrer, screen size, browser, OS, device type, and UTM params. Events are batched and flushed every 5s, or immediately on page hide via `sendBeacon`.\n\nOn `localhost` and `127.0.0.1`, the tracker skips all network requests and logs events to the browser console instead (prefixed `[aa-dev]`), so development traffic never pollutes production data.\n\n```js\nwindow.aa.track('signup', { plan: 'pro' });\nwindow.aa.identify('user_123');\nwindow.aa.page('Dashboard');\n```\n\n### Declarative event tracking\n\nTrack clicks without writing JavaScript — add `data-aa-event` to any HTML element:\n\n```html\n<button data-aa-event=\"cta_click\" data-aa-event-id=\"hero_signup\">Get Started</button>\n```\n\nWhen clicked, this fires a `cta_click` event with `{ id: \"hero_signup\" }`. Add properties with `data-aa-event-*` attributes. Use this for simple click tracking; use `window.aa.track()` for events triggered by non-click interactions or when properties need to be computed dynamically.\n\n### Script attributes\n\n| Attribute | Description |\n|-----------|-------------|\n| `data-project` | Project name (required) |\n| `data-token` | Project token `aat_*` (required) |\n| `data-link-domains` | Enable cross-subdomain identity linking |\n| `data-do-not-track` | Set to `\"true\"` to honor the browser's DNT signal |\n\nSet `localStorage.setItem('aa_disabled', 'true')` to disable tracking entirely (useful for internal teams or opt-out flows).\n\n## Reading\n\nAll read endpoints require an API key via `X-API-Key` header or `?key=` param.\n\n```bash\n# Stats overview (time series, top events, session metrics)\ncurl \"https://your-server.com/stats?project=my-site\" -H \"X-API-Key: KEY\"\n\n# Raw events\ncurl \"https://your-server.com/events?project=my-site&event=page_view&limit=50\" -H \"X-API-Key: KEY\"\n\n# Projects discovered from tracked data\ncurl \"https://your-server.com/projects\" -H \"X-API-Key: KEY\"\n```\n\n## Endpoints\n\n**Write** (project token in body):\n- `POST /track` — single event (`{ project, token, event, properties?, user_id?, session_id?, timestamp? }`)\n- `POST /track/batch` — up to 100 events (`{ events: [...] }`)\n- `POST /identify` — merge an anonymous visitor id into a known user id\n\n**Read** (API key required):\n- `GET /stats?project=X` — aggregated overview with time series, top events, sessions. Optional: `since`, `groupBy` (hour/day/week/month)\n- `GET /events?project=X` — raw event log. Optional: `event`, `session_id`, `since`, `limit`\n- `GET /projects` — all projects derived from events data\n\n**Utility:** `GET /health`, `GET /tracker.js`\n\n## Writing a database adapter\n\nThe included `D1Adapter` works with Cloudflare D1. For other databases, implement this interface:\n\n```js\nclass MyAdapter {\n  trackEvent({ project, event, properties, user_id, session_id, timestamp })\n  trackBatch(events)\n  getStats({ project, since?, groupBy? })\n  getEvents({ project, event?, session_id?, since?, limit? })\n  listProjects()\n  getSessionStats({ project, since? })\n  upsertSession(sessionData)\n  cleanupSessions({ project, before_date })\n}\n```\n\nOptional richer analytics methods like `query()` and `getProperties()` can still exist on adapters for non-OSS consumers, but the OSS public handler only exposes the endpoints listed above. All methods return promises. See `src/db/d1.js` for the reference implementation — `trackEvent` and `trackBatch` handle session upserts atomically via `db.batch()`.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}