{"id":50902943,"url":"https://github.com/stacksjs/ts-analytics","last_synced_at":"2026-06-16T04:31:30.992Z","repository":{"id":334301070,"uuid":"1106829784","full_name":"stacksjs/ts-analytics","owner":"stacksjs","description":"A privacy-first analytics toolkit for web applications, powered by DynamoDB. Includes error-tracking.","archived":false,"fork":false,"pushed_at":"2026-06-10T03:22:07.000Z","size":1856,"stargazers_count":2,"open_issues_count":38,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-10T05:09:42.095Z","etag":null,"topics":["analytics","dynamodb","fathom","heatmaps","privacy-friendly","typescript"],"latest_commit_sha":null,"homepage":"https://ts-analytics.stacksjs.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stacksjs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["stacksjs","chrisbbreuer"],"open_collective":"stacksjs"}},"created_at":"2025-11-30T02:53:01.000Z","updated_at":"2026-06-09T15:51:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/stacksjs/ts-analytics","commit_stats":null,"previous_names":["stacksjs/ts-analytics"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/stacksjs/ts-analytics","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fts-analytics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fts-analytics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fts-analytics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fts-analytics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stacksjs","download_url":"https://codeload.github.com/stacksjs/ts-analytics/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fts-analytics/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34391702,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["analytics","dynamodb","fathom","heatmaps","privacy-friendly","typescript"],"created_at":"2026-06-16T04:31:28.069Z","updated_at":"2026-06-16T04:31:30.978Z","avatar_url":"https://github.com/stacksjs.png","language":"TypeScript","funding_links":["https://github.com/sponsors/stacksjs","https://github.com/sponsors/chrisbbreuer","https://opencollective.com/stacksjs"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/stacksjs/ts-analytics/blob/main/.github/art/cover.png?raw=true\" alt=\"Social Card of this repo\"\u003e\u003c/p\u003e\n\n[![npm version][npm-version-src]][npm-version-href]\n[![GitHub Actions][github-actions-src]][github-actions-href]\n[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)\n[![npm downloads][npm-downloads-src]][npm-downloads-href]\n\n# @stacksjs/analytics\n\nA privacy-first analytics toolkit for web applications, powered by DynamoDB single-table design.\n\n## Features\n\n- **Privacy-First**: No cookies, no personal data collection, GDPR-compliant by design\n- **DynamoDB Single-Table Design**: Efficient, scalable, and cost-effective storage\n- **Real-time Analytics**: Live visitor tracking and dashboard updates\n- **Goal Tracking**: Define and track conversion goals\n- **Vue Dashboard Components**: Ready-to-use dashboard UI components\n- **Framework Agnostic**: Works with Bun, Express, Hono, AWS Lambda, and more\n- **Stacks Integration**: First-class support for the Stacks framework\n\n## Installation\n\n```bash\nbun add @stacksjs/analytics\n```\n\n## Integrate on Your Site (the 3-step recipe)\n\nThe validated pattern for adding ts-analytics to any site (docs sites included):\n\n1. **Create a project** in the dashboard (or let the first event auto-create it), then copy the one-liner from **Settings → Installation**:\n\n   ```html\n   \u003cscript defer src=\"https://your-analytics-host/api/sites/YOUR_SITE_ID/script.js\"\u003e\u003c/script\u003e\n   ```\n\n   It captures page views, SPA route changes, link \u0026 outbound clicks, engagement time, web vitals, and JS errors — no cookies, no consent banner needed.\n\n2. **Verify** with the *Verify installation* button in Settings (fires a real event through `/collect` and confirms the round trip), or just visit your site and watch the dashboard.\n\n3. **Optional hardening**: append `?stealth=true` for the unlisted beacon path, or serve it first-party from your own domain — see the [ad-blocker resilience guide](docs/guide/tracking-script.md#ad-blocker-resilience-first-party-proxy). Error tracking without analytics: grab the DSN + SDK snippet from the same Settings page.\n\n## Quick Start\n\n### 1. Set Up the Analytics Store\n\n```typescript\nimport { AnalyticsStore, createAnalyticsTable } from '@stacksjs/analytics'\nimport { DynamoDBClient } from '@aws-sdk/client-dynamodb'\nimport { CreateTableCommand, DescribeTableCommand } from '@aws-sdk/client-dynamodb'\n\nconst client = new DynamoDBClient({ region: 'us-east-1' })\n\n// Create the analytics table (one-time setup)\nawait createAnalyticsTable(client, {\n  tableName: 'my-analytics',\n  billingMode: 'PAY_PER_REQUEST',\n}, { CreateTableCommand, DescribeTableCommand })\n\n// Initialize the store\nconst store = new AnalyticsStore({\n  tableName: 'my-analytics',\n})\n```\n\n### 2. Add the Tracking Script\n\n```typescript\nimport { generateTrackingScript } from '@stacksjs/analytics'\n\nconst script = generateTrackingScript({\n  siteId: 'my-site-id',\n  endpoint: 'https://api.example.com/collect',\n  // Optional settings\n  trackPageviews: true,\n  trackOutboundLinks: true,\n  respectDoNotTrack: true,\n})\n\n// Inject into your HTML\nconst html = `\u003chtml\u003e\u003chead\u003e${script}\u003c/head\u003e...\u003c/html\u003e`\n```\n\n### 3. Create API Handlers\n\n```typescript\nimport { AnalyticsAPI, createBunRouter } from '@stacksjs/analytics'\n\nconst api = new AnalyticsAPI({\n  tableName: 'my-analytics',\n  siteId: 'my-site-id',\n})\n\n// For Bun\nconst router = createBunRouter(api, executeCommand)\n\n// For AWS Lambda\nimport { createLambdaHandler } from '@stacksjs/analytics'\nexport const handler = createLambdaHandler(api, executeCommand)\n```\n\n## Architecture: Source of Truth\n\nThe repository has two API layers — only one is live:\n\n| Layer | Status | Where |\n|---|---|---|\n| **Live server** | ✅ source of truth | `src/router.ts` + `src/handlers/*` (DynamoDB-backed), served by `server/index.ts`; jobs in `src/jobs/`, pre-aggregation in `src/lib/rollups.ts` |\n| Legacy library | ⚠️ compatibility only | `src/api.ts`, `src/stacks-integration.ts`, and the in-memory `AnalyticsStore`/`AnalyticsQueryAPI`/`AggregationPipeline` classes in `src/Analytics.ts` |\n\nNew endpoints and features belong in `src/router.ts` + `src/handlers/`. The legacy layer is kept so existing imports of the published package keep compiling; it does not serve the bundled server or dashboard. (`generateTrackingScript` in `src/Analytics.ts` **is** live — it builds the served `/script.js`.)\n\n## Core Concepts\n\n### Analytics Store\n\nThe `AnalyticsStore` provides DynamoDB operations for all analytics entities:\n\n```typescript\nconst store = new AnalyticsStore({\n  tableName: 'analytics',\n  useTtl: true,\n  rawEventTtl: 30 _ 24 _ 60 * 60, // 30 days\n})\n\n// Create a site\nconst siteCommand = store.createSiteCommand({\n  id: 'site-123',\n  name: 'My Website',\n  domains: ['example.com'],\n  ownerId: 'user-456',\n})\n\n// Record a page view\nconst pvCommand = store.recordPageViewCommand({\n  id: 'pv-789',\n  siteId: 'site-123',\n  path: '/blog/hello-world',\n  visitorId: 'visitor-hash',\n  sessionId: 'session-abc',\n  timestamp: new Date(),\n})\n```\n\n### Aggregation Pipeline\n\nPre-compute statistics for fast dashboard queries:\n\n```typescript\nimport { AggregationPipeline, AnalyticsAggregator } from '@stacksjs/analytics'\n\nconst pipeline = new AggregationPipeline({\n  tableName: 'analytics',\n})\n\n// Run hourly aggregation\nconst aggregator = new AnalyticsAggregator({\n  tableName: 'analytics',\n})\n\nawait aggregator.aggregateHourly('site-123', new Date())\n```\n\n### Query API\n\nFetch analytics data for dashboards:\n\n```typescript\nimport { AnalyticsQueryAPI } from '@stacksjs/analytics'\n\nconst queryApi = new AnalyticsQueryAPI({\n  tableName: 'analytics',\n})\n\n// Generate dashboard queries\nconst queries = queryApi.generateDashboardQueries({\n  siteId: 'site-123',\n  dateRange: { start: new Date('2024-01-01'), end: new Date() },\n})\n```\n\n### Goal Tracking\n\nDefine and track conversion goals:\n\n```typescript\nimport { GoalMatcher } from '@stacksjs/analytics'\n\nconst goals = [\n  { id: 'signup', type: 'pageview', pattern: '/signup/complete', matchType: 'exact' },\n  { id: 'purchase', type: 'event', pattern: 'purchase', matchType: 'exact' },\n]\n\nconst matcher = new GoalMatcher(goals)\n\n// Check if a page view matches any goals\nconst matches = matcher.matchPageView('/signup/complete')\n// =\u003e [{ goalId: 'signup', value: undefined }]\n```\n\n## Dashboard Components\n\nVue 3 components for building analytics dashboards:\n\n```vue\n\u003cscript setup\u003e\nimport {\n  AnalyticsDashboard,\n  StatCard,\n  TimeSeriesChart,\n  TopList,\n  DeviceBreakdown,\n  RealtimeCounter,\n  DateRangePicker,\n} from '@stacksjs/analytics'\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cAnalyticsDashboard\n    :config=\"{ baseUrl: '/api/analytics', siteId: 'my-site' }\"\n  /\u003e\n\u003c/template\u003e\n```\n\n### Composables\n\n```typescript\nimport { createAnalyticsComposable, fetchDashboardData } from '@stacksjs/analytics'\n\n// Create a composable for Vue\nconst analytics = createAnalyticsComposable({\n  baseUrl: '/api/analytics',\n  siteId: 'my-site',\n})\n\n// Or fetch data directly\nconst data = await fetchDashboardData(\n  { baseUrl: '/api/analytics', siteId: 'my-site' },\n  { startDate: new Date('2024-01-01'), endDate: new Date() }\n)\n```\n\n## Stacks Framework Integration\n\nFirst-class integration with the Stacks framework:\n\n```typescript\nimport { createAnalyticsDriver, createAnalyticsMiddleware } from '@stacksjs/analytics'\n\n// Create the driver\nconst driver = await createAnalyticsDriver({\n  tableName: 'analytics',\n  siteId: 'my-site',\n  region: 'us-east-1',\n})\n\n// Add tracking middleware\napp.use(createAnalyticsMiddleware(driver))\n\n// Server-side tracking\napp.use(createServerTrackingMiddleware(driver, {\n  excludedPaths: [/^\\/api/, /^\\/admin/],\n}))\n\n// Dashboard actions\nconst actions = createDashboardActions(driver)\nconst stats = await actions.getDashboardStats({ startDate: '2024-01-01' })\n```\n\n## Infrastructure\n\nGenerate infrastructure code for deployment:\n\n```typescript\nimport {\n  generateCloudFormationTemplate,\n  generateCdkCode,\n  generateSamTemplate,\n  generateAwsCliCommands,\n} from '@stacksjs/analytics'\n\n// CloudFormation\nconst cfn = generateCloudFormationTemplate({ tableName: 'analytics' })\n\n// AWS CDK\nconst cdk = generateCdkCode({ tableName: 'analytics' })\n\n// SAM\nconst sam = generateSamTemplate({ tableName: 'analytics' })\n\n// AWS CLI commands\nconst cli = generateAwsCliCommands({ tableName: 'analytics' })\n```\n\n## Models\n\nStacks-compatible model definitions for all analytics entities:\n\n```typescript\nimport {\n  SiteModel,\n  PageViewModel,\n  SessionModel,\n  CustomEventModel,\n  GoalModel,\n  AggregatedStatsModel,\n  // ... and more\n} from '@stacksjs/analytics'\n```\n\n## Testing\n\n```bash\nbun test\n```\n\n## Changelog\n\nPlease see our [releases](https://github.com/stacksjs/analytics/releases) page for more information on what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.\n\n## Community\n\nFor help, discussion about best practices, or any other conversation that would benefit from being searchable:\n\n[Discussions on GitHub](https://github.com/stacksjs/stacks/discussions)\n\nFor casual chit-chat with others using this package:\n\n[Join the Stacks Discord Server](https://discord.gg/stacksjs)\n\n## Postcardware\n\nStacks OSS will always stay open-sourced, and we will always love to receive postcards from wherever Stacks is used! _And we also determine the OSS dependencies we utilize._\n\nOur address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, USA\n\n## Sponsors\n\nWe would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.\n\n- [JetBrains](https://www.jetbrains.com/)\n- [The Solana Foundation](https://solana.com/)\n\n## License\n\nThe MIT License (MIT). Please see [LICENSE](LICENSE.md) for more information.\n\nMade with love by [Chris Breuer](https://github.com/chrisbbreuer) and [contributors](https://github.com/stacksjs/analytics/graphs/contributors).\n\n\u003c!-- Badges --\u003e\n[npm-version-src]: https://img.shields.io/npm/v/@stacksjs/analytics?style=flat-square\n[npm-version-href]: https://npmjs.com/package/@stacksjs/analytics\n[github-actions-src]: https://img.shields.io/github/actions/workflow/status/stacksjs/analytics/ci.yml?style=flat-square\u0026branch=main\n[github-actions-href]: https://github.com/stacksjs/analytics/actions?query=workflow%3Aci\n[npm-downloads-src]: https://img.shields.io/npm/dm/@stacksjs/analytics?style=flat-square\n[npm-downloads-href]: https://npmjs.com/package/@stacksjs/analytics\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstacksjs%2Fts-analytics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstacksjs%2Fts-analytics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstacksjs%2Fts-analytics/lists"}