BunPress Documentation
⌘ K
GuideAPIExamples

Local Development

Set up a local development environment with DynamoDB Local.

Quick Start

1. Generate Docker Compose

bunx analytics docker-compose --port 8000

This creates docker-compose.yml:

version: '3.8'
services:
  dynamodb-local:
    image: amazon/dynamodb-local:latest
    container_name: analytics-dynamodb
    ports:
      - "8000:8000"
    command: ["-jar", "DynamoDBLocal.jar", "-sharedDb", "-inMemory"]
    volumes:
      - dynamodb-data:/home/dynamodblocal/data

volumes:
  dynamodb-data:

2. Start DynamoDB Local

docker-compose up -d

3. Create the Table

import { createAnalyticsTable } from '@stacksjs/ts-analytics'
import { DynamoDBClient, CreateTableCommand, DescribeTableCommand } from '@aws-sdk/client-dynamodb'

const client = new DynamoDBClient({
  region: 'local',
  endpoint: 'http://localhost:8000',
  credentials: {
    accessKeyId: 'local',
    secretAccessKey: 'local',
  },
})

await createAnalyticsTable(client, {
  tableName: 'AnalyticsTable',
  billingMode: 'PAY_PER_REQUEST',
}, { CreateTableCommand, DescribeTableCommand })

4. Run the Development Server

// server.ts
import { AnalyticsAPI, createBunRouter, generateTrackingScript } from '@stacksjs/ts-analytics'
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'

const client = new DynamoDBClient({
  region: 'local',
  endpoint: 'http://localhost:8000',
  credentials: {
    accessKeyId: 'local',
    secretAccessKey: 'local',
  },
})

const api = new AnalyticsAPI({
  tableName: 'AnalyticsTable',
  corsOrigins: ['*'],
})

async function executeCommand(cmd: { command: string; input: Record<string, unknown> }) {
  const Command = await import('@aws-sdk/client-dynamodb').then(m => m[cmd.command])
  return client.send(new Command(cmd.input))
}

const router = createBunRouter(api, executeCommand)

Bun.serve({
  port: 3000,
  fetch(request) {
    const url = new URL(request.url)

    // Serve a test page
    if (url.pathname === '/') {
      const script = generateTrackingScript({
        siteId: 'test-site',
        apiEndpoint: 'http://localhost:3000/api/analytics',
        debug: true,
      })

      return new Response(`
        <!DOCTYPE html>
        <html>
        <head>
          <title>Analytics Test</title>
          ${script}
        </head>
        <body>
          <h1>Analytics Test Page</h1>
          <p>Open the browser console to see tracking events.</p>
          <button onclick="sa('event', 'button_click', { button: 'test' })">
            Track Event
          </button>
          <a href="https://example.com">Outbound Link</a>
        </body>
        </html>
      `, {
        headers: { 'Content-Type': 'text/html' },
      })
    }

    return router.fetch(request)
  },
})

console.log('Analytics server running at http://localhost:3000')

Run with:

bun run server.ts

Seed Data

Generate test data for development:

bunx analytics seed --sites 3 --page-views 1000 --sessions 200 --days 30

Or programmatically:

import { generateSeedData } from '@stacksjs/ts-analytics'

const seedData = generateSeedData({
  sites: 3,
  pageViewsPerSite: 1000,
  sessionsPerSite: 200,
  daysOfHistory: 30,
})

// Insert into DynamoDB
for (const item of seedData) {
  const command = {
    TableName: 'AnalyticsTable',
    Item: item,
  }
  await client.send(new PutItemCommand(command))
}

CLI Commands

The analytics CLI helps with local development:

# Print setup instructions
bunx analytics setup

# Generate AWS CLI command for table creation
bunx analytics create-table --table-name AnalyticsTable

# Generate Docker Compose file
bunx analytics docker-compose --port 8000

# Generate seed data
bunx analytics seed --sites 3

# Generate tracking script
bunx analytics tracking-script --site-id test-site --api-endpoint http://localhost:3000

Environment Configuration

Create a .env.local file:

# .env.local
AWS_ACCESS_KEY_ID=local
AWS_SECRET_ACCESS_KEY=local
AWS_REGION=local
DYNAMODB_ENDPOINT=http://localhost:8000
ANALYTICS_TABLE=AnalyticsTable

Load in your application:

import { config } from 'dotenv'
config({ path: '.env.local' })

const client = new DynamoDBClient({
  region: process.env.AWS_REGION,
  endpoint: process.env.DYNAMODB_ENDPOINT,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
})

Testing

Unit Tests

import { describe, test, expect } from 'bun:test'
import { AnalyticsStore, GoalMatcher } from '@stacksjs/ts-analytics'

describe('GoalMatcher', () => {
  test('matches exact pageview goal', () => {
    const goals = [{
      id: 'signup',
      type: 'pageview' as const,
      pattern: '/signup/complete',
      matchType: 'exact' as const,
    }]

    const matcher = new GoalMatcher(goals)
    const matches = matcher.matchPageView('/signup/complete')

    expect(matches).toHaveLength(1)
    expect(matches[0].goalId).toBe('signup')
  })
})

Integration Tests

import { describe, test, expect, beforeAll, afterAll } from 'bun:test'
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { createAnalyticsTable, AnalyticsStore } from '@stacksjs/ts-analytics'

describe('AnalyticsStore Integration', () => {
  let client: DynamoDBClient
  let store: AnalyticsStore

  beforeAll(async () => {
    client = new DynamoDBClient({
      region: 'local',
      endpoint: 'http://localhost:8000',
      credentials: { accessKeyId: 'local', secretAccessKey: 'local' },
    })

    await createAnalyticsTable(client, {
      tableName: 'TestAnalytics',
    }, { CreateTableCommand, DescribeTableCommand })

    store = new AnalyticsStore({ tableName: 'TestAnalytics' })
  })

  test('creates and retrieves site', async () => {
    const site = {
      id: 'test-site',
      name: 'Test Site',
      domains: ['test.com'],
      // ...
    }

    const createCommand = store.createSiteCommand(site)
    await executeCommand(client, createCommand)

    const getCommand = store.getSiteCommand('test-site')
    const result = await executeCommand(client, getCommand)

    expect(result.Item).toBeDefined()
  })
})

Run tests:

bun test

Debugging

Enable Debug Mode

const script = generateTrackingScript({
  siteId: 'test-site',
  apiEndpoint: 'http://localhost:3000',
  debug: true, // Enables console logging
})

DynamoDB Shell

Access DynamoDB Local directly:

# Install NoSQL Workbench or use AWS CLI
aws dynamodb scan \
  --table-name AnalyticsTable \
  --endpoint-url http://localhost:8000

View Table Contents

import { ScanCommand } from '@aws-sdk/client-dynamodb'

const result = await client.send(new ScanCommand({
  TableName: 'AnalyticsTable',
  Limit: 10,
}))

console.log('Items:', result.Items)

Hot Reload

Use Bun's watch mode for development:

bun --watch run server.ts

Or with nodemon:

bunx nodemon --exec "bun run" server.ts

VS Code Configuration

Add to .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "bun",
      "request": "launch",
      "name": "Debug Analytics Server",
      "program": "${workspaceFolder}/server.ts",
      "cwd": "${workspaceFolder}",
      "env": {
        "AWS_ACCESS_KEY_ID": "local",
        "AWS_SECRET_ACCESS_KEY": "local",
        "DYNAMODB_ENDPOINT": "http://localhost:8000"
      }
    }
  ]
}

Troubleshooting

DynamoDB Local Not Starting

# Check if port is in use
lsof -i :8000

# Try different port
docker-compose up -d -e "PORT=8001"

Table Already Exists

# Delete and recreate
aws dynamodb delete-table \
  --table-name AnalyticsTable \
  --endpoint-url http://localhost:8000

Permission Errors

DynamoDB Local doesn't enforce permissions. If you see errors, check your endpoint configuration.

Next Steps