Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/viewer/.vscode/keybindings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[
{
"key": "cmd+shift+t",
"command": "workbench.action.tasks.runTask",
"args": "Run Tests"
},
{
"key": "cmd+shift+w",
"command": "workbench.action.tasks.runTask",
"args": "Watch Tests"
},
{
"key": "cmd+shift+c",
"command": "workbench.action.tasks.runTask",
"args": "Test with Coverage"
},
{
"key": "cmd+shift+v",
"command": "workbench.action.tasks.runTask",
"args": "View Coverage Report"
},
{
"key": "cmd+shift+y",
"command": "workbench.action.tasks.runTask",
"args": "Open Cypress"
}
]
44 changes: 44 additions & 0 deletions packages/viewer/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Current Test File",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["run", "${relativeFile}"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"cwd": "${workspaceFolder}",
"env": {
"NODE_ENV": "test"
}
},
{
"name": "Debug All Tests",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["run"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"cwd": "${workspaceFolder}",
"env": {
"NODE_ENV": "test"
}
},
{
"name": "Debug Tests in Watch Mode",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["--watch"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"cwd": "${workspaceFolder}",
"env": {
"NODE_ENV": "test"
}
}
]
}
13 changes: 13 additions & 0 deletions packages/viewer/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"vitest.enable": true,
"vitest.workspaceConfig": "./vite.config.js",
"vitest.commandLine": "npm run test:watch",
"testing.automaticallyOpenPeekView": "failureInVisibleDocument",
"testing.defaultGutterClickAction": "run",
"testing.followRunningTest": "true",
"testing.openTesting": "neverOpen",
"files.watcherExclude": {
"**/coverage/**": true
},
"testing.automaticallyOpenTestResults": "neverOpen"
}
113 changes: 113 additions & 0 deletions packages/viewer/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Run Tests",
"type": "shell",
"command": "npm run test",
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": []
},
{
"label": "Watch Tests",
"type": "shell",
"command": "npm run test:watch",
"group": "test",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": []
},
{
"label": "Test with Coverage",
"type": "shell",
"command": "npm run test:cov",
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": []
},
{
"label": "View Coverage Report",
"type": "shell",
"command": "open",
"args": ["coverage/index.html"],
"group": "test",
"dependsOn": "Test with Coverage",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": false
},
"problemMatcher": []
},
{
"label": "Open Cypress",
"type": "shell",
"command": "npm run cy:open",
"group": "test",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": []
},
{
"label": "Run Single Test File",
"type": "shell",
"command": "npx vitest run ${relativeFile}",
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": []
},
{
"label": "Clean Coverage",
"type": "shell",
"command": "rm -rf coverage",
"group": "build",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": false
}
}
]
}
141 changes: 141 additions & 0 deletions packages/viewer/src/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* Cloudflare Workers Analytics Engine integration utility
*/

export interface AnalyticsEngineDataset {
writeDataPoint(data: AnalyticsDataPoint): void
}

export interface AnalyticsDataPoint {
blobs?: (string | null)[]
doubles?: number[]
indexes?: (string | null)[]
}

export interface WorkerMetrics {
duration: number
endpoint: string
method: string
status: number
userAgent?: string
country?: string
timestamp: number
}

/**
* Helper class for sending metrics to Cloudflare Analytics Engine
*/
export class AnalyticsReporter {
private analyticsEngine: AnalyticsEngineDataset | null = null

constructor(analyticsEngine?: AnalyticsEngineDataset) {
this.analyticsEngine = analyticsEngine || null
}

/**
* Report worker execution metrics to Analytics Engine
*/
reportWorkerMetrics(metrics: WorkerMetrics): void {
if (!this.analyticsEngine) {
// In development or when Analytics Engine is not available
console.log('Worker Metrics:', metrics)
return
}

try {
this.analyticsEngine.writeDataPoint({
// Indexed fields (can be queried and filtered)
indexes: [
metrics.endpoint, // index[1]: endpoint path
metrics.method, // index[2]: HTTP method
metrics.status.toString(), // index[3]: status code
metrics.country || null, // index[4]: user country
],
// Blob fields (string data)
blobs: [
metrics.userAgent || null, // blob[1]: user agent
],
// Double fields (numeric data for aggregation)
doubles: [
metrics.duration, // double[1]: duration in milliseconds
metrics.timestamp, // double[2]: timestamp
],
})
} catch (error) {
console.error('Failed to report analytics:', error)
}
}

/**
* Report custom metrics to Analytics Engine
*/
reportCustomMetric(
name: string,
value: number,
tags: Record<string, string | null> = {},
): void {
if (!this.analyticsEngine) {
console.log('Custom Metric:', { name, value, tags })
return
}

try {
const indexes = [name, null, null, null] // Start with metric name
const tagKeys = Object.keys(tags).slice(0, 3) // Limit to 3 additional tags

tagKeys.forEach((key, index) => {
indexes[index + 1] = tags[key]
})

this.analyticsEngine.writeDataPoint({
indexes: indexes as (string | null)[],
doubles: [value, Date.now()],
blobs: [null],
})
} catch (error) {
console.error('Failed to report custom metric:', error)
}
}
}

/**
* Extract country from Cloudflare request headers
*/
export function getCountryFromRequest(request: Request): string | undefined {
return request.headers.get('CF-IPCountry') || undefined
}

/**
* Create a performance timing wrapper for functions
*/
export function withTiming<T extends unknown[], R>(
fn: (...args: T) => R | Promise<R>,
onComplete: (duration: number) => void,
): (...args: T) => R | Promise<R> {
return (...args: T) => {
const startTime = performance.now()

const handleComplete = (result: R) => {
const duration = performance.now() - startTime
onComplete(duration)
return result
}

try {
const result = fn(...args)
if (result instanceof Promise) {
return result.then(handleComplete).catch(error => {
const duration = performance.now() - startTime
onComplete(duration)
throw error
})
} else {
return handleComplete(result)
}
} catch (error) {
const duration = performance.now() - startTime
onComplete(duration)
throw error
}
}
}
23 changes: 23 additions & 0 deletions packages/viewer/src/app.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
interface Platform {
env: {
ANALYTICS_ENGINE?: import('./src/analytics.js').AnalyticsEngineDataset
// Add other Cloudflare environment variables as needed
[key: string]: unknown
}
context: {
waitUntil(promise: Promise<any>): void
}
caches: CacheStorage & { default: Cache }
}
}
}

export {}
Loading
Loading