@metrone-io/server 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @metrone-io/server
2
+
3
+ Server-side SDK for [Metrone](https://metrone.io) analytics. Works with Node.js, Deno, Bun, and edge runtimes.
4
+
5
+ Track events, AI calls, and conversions from your backend. Read analytics data via the API.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @metrone-io/server
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```javascript
16
+ import { MetroneServer } from '@metrone-io/server'
17
+
18
+ const metrone = new MetroneServer({
19
+ apiKey: 'metrone_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
20
+ })
21
+
22
+ // Track events (queued for batch sending, returns void)
23
+ metrone.track('user_signup', { properties: { plan: 'pro' } })
24
+
25
+ // Track AI interactions
26
+ metrone.trackAICall({
27
+ call_id: 'call_123',
28
+ provider: 'openai',
29
+ duration: 120,
30
+ intent: 'booking',
31
+ outcome: 'converted'
32
+ })
33
+
34
+ // Read analytics (async)
35
+ const stats = await metrone.getStats({ days: 7 })
36
+ const pages = await metrone.getPages({ days: 7, limit: 10 })
37
+ const live = await metrone.getLive()
38
+
39
+ // Flush and shut down before process exit
40
+ await metrone.shutdown()
41
+ ```
42
+
43
+ ## Features
44
+
45
+ - Zero runtime dependencies
46
+ - Event tracking with batching and retries
47
+ - AI call and chat session tracking
48
+ - Read API for stats, pages, sources, events
49
+ - Live visitor count
50
+ - Works on Node.js 18+, Deno, Bun, Cloudflare Workers
51
+
52
+ ## Documentation
53
+
54
+ [metrone.io/docs](https://metrone.io/docs)
55
+
56
+ ## License
57
+
58
+ [MIT](../../LICENSE)
package/dist/index.cjs CHANGED
@@ -99,6 +99,12 @@ var MetroneServerError = class extends MetroneError {
99
99
  this.name = "MetroneServerError";
100
100
  }
101
101
  };
102
+ var MetroneClientError = class extends MetroneError {
103
+ constructor(status, message = "Request rejected by server") {
104
+ super("CLIENT_ERROR", message, status, false);
105
+ this.name = "MetroneClientError";
106
+ }
107
+ };
102
108
 
103
109
  // src/http.ts
104
110
  async function httpPost(config, path, body, headers) {
@@ -121,7 +127,7 @@ async function httpRequest(config, method, path, body, extraHeaders) {
121
127
  const fetchFn = config.fetch;
122
128
  const headers = {
123
129
  "X-Api-Key": config.apiKey,
124
- "User-Agent": "metrone-server-sdk/1.0.0",
130
+ "User-Agent": "metrone-server-sdk/1.0.2",
125
131
  ...extraHeaders
126
132
  };
127
133
  if (body !== void 0) {
@@ -155,6 +161,9 @@ async function httpRequest(config, method, path, body, extraHeaders) {
155
161
  throw new MetroneRateLimitError(retryMs);
156
162
  }
157
163
  if (response.status >= 500) throw new MetroneServerError(response.status, errMsg);
164
+ if (response.status >= 400 && response.status < 500) {
165
+ throw new MetroneClientError(response.status, errMsg);
166
+ }
158
167
  return {
159
168
  ok: false,
160
169
  status: response.status,
@@ -165,6 +174,7 @@ async function httpRequest(config, method, path, body, extraHeaders) {
165
174
  if (err instanceof MetroneAuthError) throw err;
166
175
  if (err instanceof MetroneRateLimitError) throw err;
167
176
  if (err instanceof MetroneServerError) throw err;
177
+ if (err instanceof MetroneClientError) throw err;
168
178
  if (err instanceof DOMException && err.name === "AbortError") {
169
179
  throw new MetroneTimeoutError(config.timeoutMs);
170
180
  }
@@ -455,12 +465,26 @@ var MetroneServer = class {
455
465
  }
456
466
  return { sent: accepted, failed: batch.length - accepted, errors: [] };
457
467
  } catch (err) {
468
+ const isPermanent = err instanceof MetroneAuthError || err instanceof MetroneClientError || err instanceof MetroneError && err.retryable === false;
469
+ if (isPermanent) {
470
+ if (this.config.debug) {
471
+ console.error(
472
+ `[metrone] Dropping ${batch.length} events \u2014 server rejected them permanently:`,
473
+ err instanceof Error ? err.message : err
474
+ );
475
+ }
476
+ return {
477
+ sent: 0,
478
+ failed: batch.length,
479
+ errors: [{ index: -1, error: err instanceof Error ? err.message : String(err) }]
480
+ };
481
+ }
458
482
  this.queue.unshift(...batch);
459
483
  if (this.queue.length > this.config.maxQueueSize) {
460
484
  this.queue.length = this.config.maxQueueSize;
461
485
  }
462
486
  if (this.config.debug) {
463
- console.error("[metrone] Flush failed, re-queued:", err);
487
+ console.error("[metrone] Flush failed (transient), re-queued:", err);
464
488
  }
465
489
  return {
466
490
  sent: 0,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["/**\n * @metrone-io/server — Server-side SDK for Metrone analytics.\n *\n * Works in Node.js 18+, Deno, Bun, Cloudflare Workers, and any runtime\n * with a global fetch(). Zero dependencies.\n *\n * @example\n * ```ts\n * import { MetroneServer } from '@metrone-io/server'\n *\n * const metrone = new MetroneServer({\n * apiKey: 'metrone_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',\n * })\n *\n * // Track events from your server\n * metrone.track('user_signup', { properties: { plan: 'pro' } })\n *\n * // Track AI interactions\n * metrone.trackAICall({\n * call_id: 'call_123',\n * provider: 'openai',\n * duration: 120,\n * intent: 'booking',\n * outcome: 'converted',\n * })\n *\n * // Read analytics\n * const stats = await metrone.getStats({ days: 30 })\n * console.log(stats.unique_visitors)\n *\n * // Graceful shutdown\n * await metrone.shutdown()\n * ```\n */\n\nexport { MetroneServer } from './client.js'\n\nexport {\n MetroneError,\n MetroneConfigError,\n MetroneAuthError,\n MetroneRateLimitError,\n MetroneQuotaError,\n MetroneNetworkError,\n MetroneTimeoutError,\n MetroneValidationError,\n MetroneServerError,\n} from './errors.js'\n\nexport type {\n MetroneServerConfig,\n EventPayload,\n EventSource,\n AICallData,\n AIChatData,\n AIIntentData,\n AISessionData,\n ConversionData,\n StatsParams,\n EventsParams,\n PagesParams,\n SourcesParams,\n FlushResult,\n ApiResponse,\n ApiError,\n StatsResponse,\n EventsResponse,\n EventRow,\n PagesResponse,\n PageRow,\n SourcesResponse,\n ChannelRow,\n ReferrerRow,\n LiveResponse,\n} from './types.js'\n","/**\n * Structured error types for the Metrone server SDK.\n *\n * All errors carry a machine-readable `code` that agents can switch on,\n * plus a human-readable `message` for logs.\n */\n\nexport class MetroneError extends Error {\n public readonly code: string\n public readonly status?: number\n public readonly retryable: boolean\n\n constructor(code: string, message: string, status?: number, retryable = false) {\n super(message)\n this.name = 'MetroneError'\n this.code = code\n this.status = status\n this.retryable = retryable\n }\n}\n\nexport class MetroneConfigError extends MetroneError {\n constructor(message: string) {\n super('CONFIG_INVALID', message)\n this.name = 'MetroneConfigError'\n }\n}\n\nexport class MetroneAuthError extends MetroneError {\n constructor(message = 'Invalid or missing API key') {\n super('AUTH_FAILED', message, 401, false)\n this.name = 'MetroneAuthError'\n }\n}\n\nexport class MetroneRateLimitError extends MetroneError {\n public readonly retryAfterMs: number\n\n constructor(retryAfterMs: number) {\n super('RATE_LIMITED', `Rate limit exceeded. Retry after ${retryAfterMs}ms.`, 429, true)\n this.name = 'MetroneRateLimitError'\n this.retryAfterMs = retryAfterMs\n }\n}\n\nexport class MetroneQuotaError extends MetroneError {\n constructor(message = 'Monthly event quota exceeded') {\n super('QUOTA_EXCEEDED', message, 429, false)\n this.name = 'MetroneQuotaError'\n }\n}\n\nexport class MetroneNetworkError extends MetroneError {\n constructor(message = 'Network request failed') {\n super('NETWORK_ERROR', message, undefined, true)\n this.name = 'MetroneNetworkError'\n }\n}\n\nexport class MetroneTimeoutError extends MetroneError {\n constructor(timeoutMs: number) {\n super('TIMEOUT', `Request timed out after ${timeoutMs}ms`, undefined, true)\n this.name = 'MetroneTimeoutError'\n }\n}\n\nexport class MetroneValidationError extends MetroneError {\n public readonly fields: Array<{ field: string; message: string }>\n\n constructor(fields: Array<{ field: string; message: string }>) {\n const summary = fields.map(f => `${f.field}: ${f.message}`).join('; ')\n super('VALIDATION_FAILED', `Validation failed: ${summary}`, 400, false)\n this.name = 'MetroneValidationError'\n this.fields = fields\n }\n}\n\nexport class MetroneServerError extends MetroneError {\n constructor(status: number, message = 'Internal server error') {\n super('SERVER_ERROR', message, status, true)\n this.name = 'MetroneServerError'\n }\n}\n","/**\n * Runtime-agnostic HTTP layer.\n *\n * Uses the global fetch() which is available in Node 18+, Deno, Bun,\n * Cloudflare Workers, and all modern edge runtimes. A custom fetch can\n * be injected via config for environments that need it.\n */\n\nimport type { ResolvedConfig, ApiResponse } from './types.js'\nimport {\n MetroneNetworkError,\n MetroneTimeoutError,\n MetroneAuthError,\n MetroneRateLimitError,\n MetroneServerError,\n} from './errors.js'\n\nexport async function httpPost(\n config: ResolvedConfig,\n path: string,\n body: unknown,\n headers?: Record<string, string>,\n): Promise<ApiResponse> {\n return httpRequest(config, 'POST', path, body, headers)\n}\n\nexport async function httpGet(\n config: ResolvedConfig,\n path: string,\n params?: Record<string, string | number | undefined>,\n headers?: Record<string, string>,\n): Promise<ApiResponse> {\n let url = path\n if (params) {\n const qs = new URLSearchParams()\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined) qs.set(k, String(v))\n }\n const str = qs.toString()\n if (str) url += `?${str}`\n }\n return httpRequest(config, 'GET', url, undefined, headers)\n}\n\nasync function httpRequest(\n config: ResolvedConfig,\n method: string,\n path: string,\n body?: unknown,\n extraHeaders?: Record<string, string>,\n): Promise<ApiResponse> {\n const url = `${config.endpoint}${path}`\n const fetchFn = config.fetch\n\n const headers: Record<string, string> = {\n 'X-Api-Key': config.apiKey,\n 'User-Agent': 'metrone-server-sdk/1.0.0',\n ...extraHeaders,\n }\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json'\n }\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), config.timeoutMs)\n\n try {\n const response = await fetchFn(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n })\n\n clearTimeout(timer)\n\n let data: unknown\n const ct = response.headers.get('content-type') ?? ''\n if (ct.includes('application/json')) {\n data = await response.json()\n } else {\n data = await response.text()\n }\n\n if (response.ok) {\n return { ok: true, status: response.status, data }\n }\n\n const errBody = data as Record<string, unknown> | undefined\n const errMsg = typeof errBody?.error === 'string'\n ? errBody.error\n : typeof errBody?.error === 'object' && errBody.error !== null\n ? (errBody.error as Record<string, unknown>).message as string ?? response.statusText\n : response.statusText\n\n if (response.status === 401) throw new MetroneAuthError(errMsg)\n if (response.status === 429) {\n const retryMs = typeof errBody?.retry_after_ms === 'number'\n ? errBody.retry_after_ms\n : 60_000\n throw new MetroneRateLimitError(retryMs)\n }\n if (response.status >= 500) throw new MetroneServerError(response.status, errMsg)\n\n return {\n ok: false,\n status: response.status,\n error: { code: 'API_ERROR', message: errMsg, details: errBody },\n }\n } catch (err) {\n clearTimeout(timer)\n\n if (err instanceof MetroneAuthError) throw err\n if (err instanceof MetroneRateLimitError) throw err\n if (err instanceof MetroneServerError) throw err\n\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw new MetroneTimeoutError(config.timeoutMs)\n }\n if (err instanceof TypeError) {\n throw new MetroneNetworkError(err.message)\n }\n\n throw new MetroneNetworkError(\n err instanceof Error ? err.message : 'Unknown network error',\n )\n }\n}\n\n/**\n * Retry wrapper with exponential backoff.\n * Retries on MetroneNetworkError, MetroneTimeoutError, MetroneServerError,\n * and MetroneRateLimitError. Stops on auth or validation errors.\n */\nexport async function withRetry<T>(\n config: ResolvedConfig,\n fn: () => Promise<T>,\n): Promise<T> {\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn()\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err))\n\n const isRetryable =\n err instanceof MetroneNetworkError ||\n err instanceof MetroneTimeoutError ||\n err instanceof MetroneServerError ||\n err instanceof MetroneRateLimitError\n\n if (!isRetryable || attempt >= config.maxRetries) throw err\n\n const baseDelay = err instanceof MetroneRateLimitError\n ? err.retryAfterMs\n : config.retryBaseMs * Math.pow(2, attempt)\n\n const jitter = Math.random() * baseDelay * 0.1\n const delay = baseDelay + jitter\n\n if (config.debug) {\n const errLabel = err instanceof Error ? err.message : String(err)\n console.error(\n `[metrone] Retry ${attempt + 1}/${config.maxRetries} after ${Math.round(delay)}ms: ${errLabel}`,\n )\n }\n\n await sleep(delay)\n }\n }\n\n throw lastError\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n","/**\n * MetroneServer — the main server-side SDK client.\n *\n * Zero dependencies. Uses global fetch (Node 18+, Deno, Bun, edge runtimes).\n * Runtime-agnostic: no DOM, no window, no navigator, no localStorage.\n *\n * Features:\n * - Event ingestion (single + batch) with automatic batching\n * - AI tracking (calls, chats, intents, sessions)\n * - Read API (stats, events, pages, sources, live)\n * - Retry with exponential backoff + jitter\n * - Idempotency key support\n * - Agent identity tracking\n * - Graceful shutdown with flush()\n */\n\nimport type {\n MetroneServerConfig,\n ResolvedConfig,\n EventPayload,\n AICallData,\n AIChatData,\n AIIntentData,\n AISessionData,\n ConversionData,\n StatsParams,\n EventsParams,\n PagesParams,\n SourcesParams,\n FlushResult,\n StatsResponse,\n EventsResponse,\n PagesResponse,\n SourcesResponse,\n LiveResponse,\n ApiResponse,\n} from './types.js'\nimport { MetroneConfigError, MetroneValidationError } from './errors.js'\nimport { httpPost, httpGet, withRetry } from './http.js'\n\nconst API_KEY_PATTERN = /^(metrone_(live|test)_|mk_(live|test)_)[a-f0-9]{32}$/\n\nexport class MetroneServer {\n private readonly config: ResolvedConfig\n private queue: Array<EventPayload & { api_key: string }> = []\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private flushing = false\n private destroyed = false\n\n constructor(config: MetroneServerConfig) {\n if (!config.apiKey) {\n throw new MetroneConfigError('apiKey is required')\n }\n if (!API_KEY_PATTERN.test(config.apiKey)) {\n throw new MetroneConfigError(\n 'Invalid apiKey format. Expected metrone_live_*, metrone_test_*, mk_live_*, or mk_test_* followed by 32 hex chars.',\n )\n }\n\n const endpoint = (config.endpoint ?? 'https://api.metrone.io').replace(/\\/+$/, '')\n\n this.config = {\n apiKey: config.apiKey,\n endpoint,\n batchSize: config.batchSize ?? 10,\n flushIntervalMs: config.flushIntervalMs ?? 5000,\n maxRetries: config.maxRetries ?? 3,\n retryBaseMs: config.retryBaseMs ?? 1000,\n timeoutMs: config.timeoutMs ?? 10_000,\n maxQueueSize: config.maxQueueSize ?? 1000,\n debug: config.debug ?? false,\n fetch: config.fetch ?? globalThis.fetch.bind(globalThis),\n }\n\n if (this.config.batchSize > 0 && this.config.flushIntervalMs > 0) {\n this.flushTimer = setInterval(() => {\n this.flush().catch(err => {\n if (this.config.debug) {\n console.error('[metrone] Auto-flush error:', err)\n }\n })\n }, this.config.flushIntervalMs)\n\n if (typeof this.flushTimer === 'object' && 'unref' in this.flushTimer) {\n (this.flushTimer as NodeJS.Timeout).unref()\n }\n }\n\n if (this.config.debug) {\n console.error(`[metrone] Server SDK initialized (endpoint: ${this.config.endpoint})`)\n }\n }\n\n // ─── Event Tracking ────────────────────────────────────────────────────────\n\n /**\n * Track a custom event. Queued for batch sending unless batchSize is 0.\n */\n track(eventType: string, data?: Partial<EventPayload>): void {\n this.assertNotDestroyed()\n if (!eventType) throw new MetroneValidationError([{ field: 'event_type', message: 'required' }])\n\n const event: EventPayload & { api_key: string } = {\n api_key: this.config.apiKey,\n event_type: eventType,\n timestamp: new Date().toISOString(),\n ...data,\n }\n\n if (this.config.batchSize <= 0) {\n withRetry(this.config, () => httpPost(this.config, '/v1/events', event)).catch(err => {\n if (this.config.debug) console.error('[metrone] Send error:', err)\n })\n return\n }\n\n this.queue.push(event)\n if (this.queue.length >= this.config.batchSize) {\n this.flush().catch(err => {\n if (this.config.debug) console.error('[metrone] Flush error:', err)\n })\n }\n }\n\n /**\n * Track a page view.\n */\n pageview(url: string, title?: string, data?: Partial<EventPayload>): void {\n this.track('pageview', {\n page_url: url,\n page_title: title,\n source: 'web',\n ...data,\n })\n }\n\n /**\n * Track a conversion event.\n */\n conversion(data: ConversionData): void {\n this.track('conversion', {\n source: 'web',\n properties: {\n conversion_type: data.conversion_type,\n value: data.value,\n currency: data.currency,\n ...data.properties,\n },\n })\n }\n\n // ─── AI Tracking ───────────────────────────────────────────────────────────\n\n /**\n * Track an AI voice call.\n */\n trackAICall(data: AICallData): void {\n this.track('ai_call', {\n source: 'voice',\n channel: 'phone',\n ai_provider: data.provider ?? 'unknown',\n ai_call_id: data.call_id,\n ai_intent: data.intent,\n ai_duration_sec: data.duration,\n session_id: data.session_id,\n properties: {\n transcript_snippet: data.transcript_snippet,\n outcome: data.outcome,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI chat interaction.\n */\n trackAIChat(data: AIChatData): void {\n this.track('ai_chat', {\n source: 'chat',\n channel: 'website',\n ai_provider: data.provider ?? 'unknown',\n ai_session_id: data.session_id,\n ai_intent: data.intent,\n ai_duration_sec: data.duration,\n properties: {\n message_count: data.message_count,\n resolved: data.resolved,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI intent detection.\n */\n trackAIIntent(data: AIIntentData): void {\n this.track('ai_intent', {\n source: data.source ?? 'assistant',\n ai_intent: data.intent,\n properties: {\n confidence: data.confidence,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI session lifecycle event (start, end, timeout).\n */\n trackAISession(data: AISessionData): void {\n this.track(`ai_session_${data.action}`, {\n source: 'assistant',\n ai_provider: data.provider ?? 'unknown',\n ai_session_id: data.session_id,\n ai_duration_sec: data.duration,\n properties: data.properties,\n })\n }\n\n // ─── Read API ──────────────────────────────────────────────────────────────\n\n /**\n * Get aggregated analytics stats for a time period.\n */\n async getStats(params?: StatsParams): Promise<StatsResponse> {\n this.assertNotDestroyed()\n const qp = this.buildDateParams(params)\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/stats', qp),\n )\n return res.data as StatsResponse\n }\n\n /**\n * Get individual analytics events with pagination and filters.\n */\n async getEvents(params?: EventsParams): Promise<EventsResponse> {\n this.assertNotDestroyed()\n const qp: Record<string, string | number | undefined> = {\n ...this.buildDateParams(params),\n limit: params?.limit,\n offset: params?.offset,\n event_type: params?.event_type,\n source: params?.source,\n }\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/events', qp),\n )\n return res.data as EventsResponse\n }\n\n /**\n * Get page analytics broken down by path.\n */\n async getPages(params?: PagesParams): Promise<PagesResponse> {\n this.assertNotDestroyed()\n const qp: Record<string, string | number | undefined> = {\n ...this.buildDateParams(params),\n limit: params?.limit,\n }\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/pages', qp),\n )\n return res.data as PagesResponse\n }\n\n /**\n * Get traffic source and referrer analytics.\n */\n async getSources(params?: SourcesParams): Promise<SourcesResponse> {\n this.assertNotDestroyed()\n const qp = this.buildDateParams(params)\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/sources', qp),\n )\n return res.data as SourcesResponse\n }\n\n /**\n * Get real-time live stats (active visitors, today's totals).\n */\n async getLive(): Promise<LiveResponse> {\n this.assertNotDestroyed()\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/live'),\n )\n return res.data as LiveResponse\n }\n\n // ─── Batch & Lifecycle ─────────────────────────────────────────────────────\n\n /**\n * Immediately flush all queued events. Returns the result.\n * Safe to call multiple times concurrently — only one flush runs at a time.\n */\n async flush(): Promise<FlushResult> {\n if (this.flushing || this.queue.length === 0) {\n return { sent: 0, failed: 0, errors: [] }\n }\n\n this.flushing = true\n const batch = this.queue.splice(0, 100)\n\n try {\n const res = await withRetry(this.config, () =>\n httpPost(this.config, '/v1/events/batch', batch),\n )\n\n const body = res.data as Record<string, unknown> | undefined\n const accepted = typeof body?.accepted === 'number' ? body.accepted : batch.length\n\n if (this.config.debug) {\n console.error(`[metrone] Flushed ${accepted}/${batch.length} events`)\n }\n\n return { sent: accepted, failed: batch.length - accepted, errors: [] }\n } catch (err) {\n this.queue.unshift(...batch)\n if (this.queue.length > this.config.maxQueueSize) {\n this.queue.length = this.config.maxQueueSize\n }\n\n if (this.config.debug) {\n console.error('[metrone] Flush failed, re-queued:', err)\n }\n\n return {\n sent: 0,\n failed: batch.length,\n errors: [{ index: -1, error: err instanceof Error ? err.message : String(err) }],\n }\n } finally {\n this.flushing = false\n\n if (this.queue.length >= this.config.batchSize && this.config.batchSize > 0) {\n this.flush().catch(() => {})\n }\n }\n }\n\n /**\n * Flush remaining events, stop the auto-flush timer, and mark the\n * client as destroyed. Subsequent calls will throw.\n */\n async shutdown(): Promise<FlushResult> {\n if (this.destroyed) return { sent: 0, failed: 0, errors: [] }\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n const result = await this.flush()\n this.destroyed = true\n\n if (this.config.debug) {\n console.error('[metrone] Server SDK shut down')\n }\n\n return result\n }\n\n /** Number of events currently in the send queue. */\n get queueSize(): number {\n return this.queue.length\n }\n\n /** Whether shutdown() has been called. */\n get isDestroyed(): boolean {\n return this.destroyed\n }\n\n // ─── Internal ──────────────────────────────────────────────────────────────\n\n private assertNotDestroyed(): void {\n if (this.destroyed) {\n throw new MetroneConfigError('Client has been shut down. Create a new instance.')\n }\n }\n\n private buildDateParams(\n params?: { days?: number; from?: string | Date; to?: string | Date },\n ): Record<string, string | number | undefined> {\n const result: Record<string, string | number | undefined> = {}\n if (params?.days) result.days = params.days\n if (params?.from) result.from = params.from instanceof Date ? params.from.toISOString() : params.from\n if (params?.to) result.to = params.to instanceof Date ? params.to.toISOString() : params.to\n return result\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["/**\n * @metrone-io/server — Server-side SDK for Metrone analytics.\n *\n * Works in Node.js 18+, Deno, Bun, Cloudflare Workers, and any runtime\n * with a global fetch(). Zero dependencies.\n *\n * @example\n * ```ts\n * import { MetroneServer } from '@metrone-io/server'\n *\n * const metrone = new MetroneServer({\n * apiKey: 'metrone_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',\n * })\n *\n * // Track events from your server\n * metrone.track('user_signup', { properties: { plan: 'pro' } })\n *\n * // Track AI interactions\n * metrone.trackAICall({\n * call_id: 'call_123',\n * provider: 'openai',\n * duration: 120,\n * intent: 'booking',\n * outcome: 'converted',\n * })\n *\n * // Read analytics\n * const stats = await metrone.getStats({ days: 30 })\n * console.log(stats.unique_visitors)\n *\n * // Graceful shutdown\n * await metrone.shutdown()\n * ```\n */\n\nexport { MetroneServer } from './client.js'\n\nexport {\n MetroneError,\n MetroneConfigError,\n MetroneAuthError,\n MetroneRateLimitError,\n MetroneQuotaError,\n MetroneNetworkError,\n MetroneTimeoutError,\n MetroneValidationError,\n MetroneServerError,\n} from './errors.js'\n\nexport type {\n MetroneServerConfig,\n EventPayload,\n EventSource,\n AICallData,\n AIChatData,\n AIIntentData,\n AISessionData,\n ConversionData,\n StatsParams,\n EventsParams,\n PagesParams,\n SourcesParams,\n FlushResult,\n ApiResponse,\n ApiError,\n StatsResponse,\n EventsResponse,\n EventRow,\n PagesResponse,\n PageRow,\n SourcesResponse,\n ChannelRow,\n ReferrerRow,\n LiveResponse,\n} from './types.js'\n","/**\n * Structured error types for the Metrone server SDK.\n *\n * All errors carry a machine-readable `code` that agents can switch on,\n * plus a human-readable `message` for logs.\n */\n\nexport class MetroneError extends Error {\n public readonly code: string\n public readonly status?: number\n public readonly retryable: boolean\n\n constructor(code: string, message: string, status?: number, retryable = false) {\n super(message)\n this.name = 'MetroneError'\n this.code = code\n this.status = status\n this.retryable = retryable\n }\n}\n\nexport class MetroneConfigError extends MetroneError {\n constructor(message: string) {\n super('CONFIG_INVALID', message)\n this.name = 'MetroneConfigError'\n }\n}\n\nexport class MetroneAuthError extends MetroneError {\n constructor(message = 'Invalid or missing API key') {\n super('AUTH_FAILED', message, 401, false)\n this.name = 'MetroneAuthError'\n }\n}\n\nexport class MetroneRateLimitError extends MetroneError {\n public readonly retryAfterMs: number\n\n constructor(retryAfterMs: number) {\n super('RATE_LIMITED', `Rate limit exceeded. Retry after ${retryAfterMs}ms.`, 429, true)\n this.name = 'MetroneRateLimitError'\n this.retryAfterMs = retryAfterMs\n }\n}\n\nexport class MetroneQuotaError extends MetroneError {\n constructor(message = 'Monthly event quota exceeded') {\n super('QUOTA_EXCEEDED', message, 429, false)\n this.name = 'MetroneQuotaError'\n }\n}\n\nexport class MetroneNetworkError extends MetroneError {\n constructor(message = 'Network request failed') {\n super('NETWORK_ERROR', message, undefined, true)\n this.name = 'MetroneNetworkError'\n }\n}\n\nexport class MetroneTimeoutError extends MetroneError {\n constructor(timeoutMs: number) {\n super('TIMEOUT', `Request timed out after ${timeoutMs}ms`, undefined, true)\n this.name = 'MetroneTimeoutError'\n }\n}\n\nexport class MetroneValidationError extends MetroneError {\n public readonly fields: Array<{ field: string; message: string }>\n\n constructor(fields: Array<{ field: string; message: string }>) {\n const summary = fields.map(f => `${f.field}: ${f.message}`).join('; ')\n super('VALIDATION_FAILED', `Validation failed: ${summary}`, 400, false)\n this.name = 'MetroneValidationError'\n this.fields = fields\n }\n}\n\nexport class MetroneServerError extends MetroneError {\n constructor(status: number, message = 'Internal server error') {\n super('SERVER_ERROR', message, status, true)\n this.name = 'MetroneServerError'\n }\n}\n\n/**\n * 4xx response that isn't one of the more specific subclasses (auth, quota,\n * validation, rate-limit). Always permanent — re-sending the same payload\n * will produce the same error, so callers must NOT requeue events that\n * triggered it. This breaks the retry-storm pattern where a permanent server\n * rejection (e.g. `BATCH_ALL_INVALID`) used to be re-queued every flush\n * interval forever.\n */\nexport class MetroneClientError extends MetroneError {\n constructor(status: number, message = 'Request rejected by server') {\n super('CLIENT_ERROR', message, status, false)\n this.name = 'MetroneClientError'\n }\n}\n","/**\n * Runtime-agnostic HTTP layer.\n *\n * Uses the global fetch() which is available in Node 18+, Deno, Bun,\n * Cloudflare Workers, and all modern edge runtimes. A custom fetch can\n * be injected via config for environments that need it.\n */\n\nimport type { ResolvedConfig, ApiResponse } from './types.js'\nimport {\n MetroneNetworkError,\n MetroneTimeoutError,\n MetroneAuthError,\n MetroneRateLimitError,\n MetroneServerError,\n MetroneClientError,\n} from './errors.js'\n\nexport async function httpPost(\n config: ResolvedConfig,\n path: string,\n body: unknown,\n headers?: Record<string, string>,\n): Promise<ApiResponse> {\n return httpRequest(config, 'POST', path, body, headers)\n}\n\nexport async function httpGet(\n config: ResolvedConfig,\n path: string,\n params?: Record<string, string | number | undefined>,\n headers?: Record<string, string>,\n): Promise<ApiResponse> {\n let url = path\n if (params) {\n const qs = new URLSearchParams()\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined) qs.set(k, String(v))\n }\n const str = qs.toString()\n if (str) url += `?${str}`\n }\n return httpRequest(config, 'GET', url, undefined, headers)\n}\n\nasync function httpRequest(\n config: ResolvedConfig,\n method: string,\n path: string,\n body?: unknown,\n extraHeaders?: Record<string, string>,\n): Promise<ApiResponse> {\n const url = `${config.endpoint}${path}`\n const fetchFn = config.fetch\n\n const headers: Record<string, string> = {\n 'X-Api-Key': config.apiKey,\n 'User-Agent': 'metrone-server-sdk/1.0.2',\n ...extraHeaders,\n }\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json'\n }\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), config.timeoutMs)\n\n try {\n const response = await fetchFn(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n })\n\n clearTimeout(timer)\n\n let data: unknown\n const ct = response.headers.get('content-type') ?? ''\n if (ct.includes('application/json')) {\n data = await response.json()\n } else {\n data = await response.text()\n }\n\n if (response.ok) {\n return { ok: true, status: response.status, data }\n }\n\n const errBody = data as Record<string, unknown> | undefined\n const errMsg = typeof errBody?.error === 'string'\n ? errBody.error\n : typeof errBody?.error === 'object' && errBody.error !== null\n ? (errBody.error as Record<string, unknown>).message as string ?? response.statusText\n : response.statusText\n\n if (response.status === 401) throw new MetroneAuthError(errMsg)\n if (response.status === 429) {\n const retryMs = typeof errBody?.retry_after_ms === 'number'\n ? errBody.retry_after_ms\n : 60_000\n throw new MetroneRateLimitError(retryMs)\n }\n if (response.status >= 500) throw new MetroneServerError(response.status, errMsg)\n // Any other 4xx (validation failure, quota exceeded as 4xx, etc.) is a\n // permanent rejection. Throw a typed error so `withRetry` doesn't loop\n // and `flush()` knows to drop the events instead of re-queueing them.\n if (response.status >= 400 && response.status < 500) {\n throw new MetroneClientError(response.status, errMsg)\n }\n\n return {\n ok: false,\n status: response.status,\n error: { code: 'API_ERROR', message: errMsg, details: errBody },\n }\n } catch (err) {\n clearTimeout(timer)\n\n if (err instanceof MetroneAuthError) throw err\n if (err instanceof MetroneRateLimitError) throw err\n if (err instanceof MetroneServerError) throw err\n if (err instanceof MetroneClientError) throw err\n\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw new MetroneTimeoutError(config.timeoutMs)\n }\n if (err instanceof TypeError) {\n throw new MetroneNetworkError(err.message)\n }\n\n throw new MetroneNetworkError(\n err instanceof Error ? err.message : 'Unknown network error',\n )\n }\n}\n\n/**\n * Retry wrapper with exponential backoff.\n * Retries on MetroneNetworkError, MetroneTimeoutError, MetroneServerError,\n * and MetroneRateLimitError. Stops on auth or validation errors.\n */\nexport async function withRetry<T>(\n config: ResolvedConfig,\n fn: () => Promise<T>,\n): Promise<T> {\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn()\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err))\n\n const isRetryable =\n err instanceof MetroneNetworkError ||\n err instanceof MetroneTimeoutError ||\n err instanceof MetroneServerError ||\n err instanceof MetroneRateLimitError\n\n if (!isRetryable || attempt >= config.maxRetries) throw err\n\n const baseDelay = err instanceof MetroneRateLimitError\n ? err.retryAfterMs\n : config.retryBaseMs * Math.pow(2, attempt)\n\n const jitter = Math.random() * baseDelay * 0.1\n const delay = baseDelay + jitter\n\n if (config.debug) {\n const errLabel = err instanceof Error ? err.message : String(err)\n console.error(\n `[metrone] Retry ${attempt + 1}/${config.maxRetries} after ${Math.round(delay)}ms: ${errLabel}`,\n )\n }\n\n await sleep(delay)\n }\n }\n\n throw lastError\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n","/**\n * MetroneServer — the main server-side SDK client.\n *\n * Zero dependencies. Uses global fetch (Node 18+, Deno, Bun, edge runtimes).\n * Runtime-agnostic: no DOM, no window, no navigator, no localStorage.\n *\n * Features:\n * - Event ingestion (single + batch) with automatic batching\n * - AI tracking (calls, chats, intents, sessions)\n * - Read API (stats, events, pages, sources, live)\n * - Retry with exponential backoff + jitter\n * - Idempotency key support\n * - Agent identity tracking\n * - Graceful shutdown with flush()\n */\n\nimport type {\n MetroneServerConfig,\n ResolvedConfig,\n EventPayload,\n AICallData,\n AIChatData,\n AIIntentData,\n AISessionData,\n ConversionData,\n StatsParams,\n EventsParams,\n PagesParams,\n SourcesParams,\n FlushResult,\n StatsResponse,\n EventsResponse,\n PagesResponse,\n SourcesResponse,\n LiveResponse,\n ApiResponse,\n} from './types.js'\nimport {\n MetroneConfigError,\n MetroneValidationError,\n MetroneClientError,\n MetroneAuthError,\n MetroneError,\n} from './errors.js'\nimport { httpPost, httpGet, withRetry } from './http.js'\n\nconst API_KEY_PATTERN = /^(metrone_(live|test)_|mk_(live|test)_)[a-f0-9]{32}$/\n\nexport class MetroneServer {\n private readonly config: ResolvedConfig\n private queue: Array<EventPayload & { api_key: string }> = []\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private flushing = false\n private destroyed = false\n\n constructor(config: MetroneServerConfig) {\n if (!config.apiKey) {\n throw new MetroneConfigError('apiKey is required')\n }\n if (!API_KEY_PATTERN.test(config.apiKey)) {\n throw new MetroneConfigError(\n 'Invalid apiKey format. Expected metrone_live_*, metrone_test_*, mk_live_*, or mk_test_* followed by 32 hex chars.',\n )\n }\n\n const endpoint = (config.endpoint ?? 'https://api.metrone.io').replace(/\\/+$/, '')\n\n this.config = {\n apiKey: config.apiKey,\n endpoint,\n batchSize: config.batchSize ?? 10,\n flushIntervalMs: config.flushIntervalMs ?? 5000,\n maxRetries: config.maxRetries ?? 3,\n retryBaseMs: config.retryBaseMs ?? 1000,\n timeoutMs: config.timeoutMs ?? 10_000,\n maxQueueSize: config.maxQueueSize ?? 1000,\n debug: config.debug ?? false,\n fetch: config.fetch ?? globalThis.fetch.bind(globalThis),\n }\n\n if (this.config.batchSize > 0 && this.config.flushIntervalMs > 0) {\n this.flushTimer = setInterval(() => {\n this.flush().catch(err => {\n if (this.config.debug) {\n console.error('[metrone] Auto-flush error:', err)\n }\n })\n }, this.config.flushIntervalMs)\n\n if (typeof this.flushTimer === 'object' && 'unref' in this.flushTimer) {\n (this.flushTimer as NodeJS.Timeout).unref()\n }\n }\n\n if (this.config.debug) {\n console.error(`[metrone] Server SDK initialized (endpoint: ${this.config.endpoint})`)\n }\n }\n\n // ─── Event Tracking ────────────────────────────────────────────────────────\n\n /**\n * Track a custom event. Queued for batch sending unless batchSize is 0.\n */\n track(eventType: string, data?: Partial<EventPayload>): void {\n this.assertNotDestroyed()\n if (!eventType) throw new MetroneValidationError([{ field: 'event_type', message: 'required' }])\n\n const event: EventPayload & { api_key: string } = {\n api_key: this.config.apiKey,\n event_type: eventType,\n timestamp: new Date().toISOString(),\n ...data,\n }\n\n if (this.config.batchSize <= 0) {\n withRetry(this.config, () => httpPost(this.config, '/v1/events', event)).catch(err => {\n if (this.config.debug) console.error('[metrone] Send error:', err)\n })\n return\n }\n\n this.queue.push(event)\n if (this.queue.length >= this.config.batchSize) {\n this.flush().catch(err => {\n if (this.config.debug) console.error('[metrone] Flush error:', err)\n })\n }\n }\n\n /**\n * Track a page view.\n */\n pageview(url: string, title?: string, data?: Partial<EventPayload>): void {\n this.track('pageview', {\n page_url: url,\n page_title: title,\n source: 'web',\n ...data,\n })\n }\n\n /**\n * Track a conversion event.\n */\n conversion(data: ConversionData): void {\n this.track('conversion', {\n source: 'web',\n properties: {\n conversion_type: data.conversion_type,\n value: data.value,\n currency: data.currency,\n ...data.properties,\n },\n })\n }\n\n // ─── AI Tracking ───────────────────────────────────────────────────────────\n\n /**\n * Track an AI voice call.\n */\n trackAICall(data: AICallData): void {\n this.track('ai_call', {\n source: 'voice',\n channel: 'phone',\n ai_provider: data.provider ?? 'unknown',\n ai_call_id: data.call_id,\n ai_intent: data.intent,\n ai_duration_sec: data.duration,\n session_id: data.session_id,\n properties: {\n transcript_snippet: data.transcript_snippet,\n outcome: data.outcome,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI chat interaction.\n */\n trackAIChat(data: AIChatData): void {\n this.track('ai_chat', {\n source: 'chat',\n channel: 'website',\n ai_provider: data.provider ?? 'unknown',\n ai_session_id: data.session_id,\n ai_intent: data.intent,\n ai_duration_sec: data.duration,\n properties: {\n message_count: data.message_count,\n resolved: data.resolved,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI intent detection.\n */\n trackAIIntent(data: AIIntentData): void {\n this.track('ai_intent', {\n source: data.source ?? 'assistant',\n ai_intent: data.intent,\n properties: {\n confidence: data.confidence,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI session lifecycle event (start, end, timeout).\n */\n trackAISession(data: AISessionData): void {\n this.track(`ai_session_${data.action}`, {\n source: 'assistant',\n ai_provider: data.provider ?? 'unknown',\n ai_session_id: data.session_id,\n ai_duration_sec: data.duration,\n properties: data.properties,\n })\n }\n\n // ─── Read API ──────────────────────────────────────────────────────────────\n\n /**\n * Get aggregated analytics stats for a time period.\n */\n async getStats(params?: StatsParams): Promise<StatsResponse> {\n this.assertNotDestroyed()\n const qp = this.buildDateParams(params)\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/stats', qp),\n )\n return res.data as StatsResponse\n }\n\n /**\n * Get individual analytics events with pagination and filters.\n */\n async getEvents(params?: EventsParams): Promise<EventsResponse> {\n this.assertNotDestroyed()\n const qp: Record<string, string | number | undefined> = {\n ...this.buildDateParams(params),\n limit: params?.limit,\n offset: params?.offset,\n event_type: params?.event_type,\n source: params?.source,\n }\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/events', qp),\n )\n return res.data as EventsResponse\n }\n\n /**\n * Get page analytics broken down by path.\n */\n async getPages(params?: PagesParams): Promise<PagesResponse> {\n this.assertNotDestroyed()\n const qp: Record<string, string | number | undefined> = {\n ...this.buildDateParams(params),\n limit: params?.limit,\n }\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/pages', qp),\n )\n return res.data as PagesResponse\n }\n\n /**\n * Get traffic source and referrer analytics.\n */\n async getSources(params?: SourcesParams): Promise<SourcesResponse> {\n this.assertNotDestroyed()\n const qp = this.buildDateParams(params)\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/sources', qp),\n )\n return res.data as SourcesResponse\n }\n\n /**\n * Get real-time live stats (active visitors, today's totals).\n */\n async getLive(): Promise<LiveResponse> {\n this.assertNotDestroyed()\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/live'),\n )\n return res.data as LiveResponse\n }\n\n // ─── Batch & Lifecycle ─────────────────────────────────────────────────────\n\n /**\n * Immediately flush all queued events. Returns the result.\n * Safe to call multiple times concurrently — only one flush runs at a time.\n */\n async flush(): Promise<FlushResult> {\n if (this.flushing || this.queue.length === 0) {\n return { sent: 0, failed: 0, errors: [] }\n }\n\n this.flushing = true\n const batch = this.queue.splice(0, 100)\n\n try {\n const res = await withRetry(this.config, () =>\n httpPost(this.config, '/v1/events/batch', batch),\n )\n\n const body = res.data as Record<string, unknown> | undefined\n const accepted = typeof body?.accepted === 'number' ? body.accepted : batch.length\n\n if (this.config.debug) {\n console.error(`[metrone] Flushed ${accepted}/${batch.length} events`)\n }\n\n return { sent: accepted, failed: batch.length - accepted, errors: [] }\n } catch (err) {\n // Permanent failures (auth, validation, other 4xx) MUST NOT be\n // re-queued — re-sending the same payload will produce the same error,\n // turning every flushIntervalMs tick into a retry storm. Drop the\n // events and surface the failure to the caller instead.\n const isPermanent =\n err instanceof MetroneAuthError ||\n err instanceof MetroneClientError ||\n (err instanceof MetroneError && err.retryable === false)\n\n if (isPermanent) {\n if (this.config.debug) {\n console.error(\n `[metrone] Dropping ${batch.length} events — server rejected them permanently:`,\n err instanceof Error ? err.message : err,\n )\n }\n return {\n sent: 0,\n failed: batch.length,\n errors: [{ index: -1, error: err instanceof Error ? err.message : String(err) }],\n }\n }\n\n // Transient failures (network, timeout, 5xx, 429 after retries) — keep\n // events for the next flush attempt.\n this.queue.unshift(...batch)\n if (this.queue.length > this.config.maxQueueSize) {\n this.queue.length = this.config.maxQueueSize\n }\n\n if (this.config.debug) {\n console.error('[metrone] Flush failed (transient), re-queued:', err)\n }\n\n return {\n sent: 0,\n failed: batch.length,\n errors: [{ index: -1, error: err instanceof Error ? err.message : String(err) }],\n }\n } finally {\n this.flushing = false\n\n if (this.queue.length >= this.config.batchSize && this.config.batchSize > 0) {\n this.flush().catch(() => {})\n }\n }\n }\n\n /**\n * Flush remaining events, stop the auto-flush timer, and mark the\n * client as destroyed. Subsequent calls will throw.\n */\n async shutdown(): Promise<FlushResult> {\n if (this.destroyed) return { sent: 0, failed: 0, errors: [] }\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n const result = await this.flush()\n this.destroyed = true\n\n if (this.config.debug) {\n console.error('[metrone] Server SDK shut down')\n }\n\n return result\n }\n\n /** Number of events currently in the send queue. */\n get queueSize(): number {\n return this.queue.length\n }\n\n /** Whether shutdown() has been called. */\n get isDestroyed(): boolean {\n return this.destroyed\n }\n\n // ─── Internal ──────────────────────────────────────────────────────────────\n\n private assertNotDestroyed(): void {\n if (this.destroyed) {\n throw new MetroneConfigError('Client has been shut down. Create a new instance.')\n }\n }\n\n private buildDateParams(\n params?: { days?: number; from?: string | Date; to?: string | Date },\n ): Record<string, string | number | undefined> {\n const result: Record<string, string | number | undefined> = {}\n if (params?.days) result.days = params.days\n if (params?.from) result.from = params.from instanceof Date ? params.from.toISOString() : params.from\n if (params?.to) result.to = params.to instanceof Date ? params.to.toISOString() : params.to\n return result\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -64,6 +64,12 @@ var MetroneServerError = class extends MetroneError {
64
64
  this.name = "MetroneServerError";
65
65
  }
66
66
  };
67
+ var MetroneClientError = class extends MetroneError {
68
+ constructor(status, message = "Request rejected by server") {
69
+ super("CLIENT_ERROR", message, status, false);
70
+ this.name = "MetroneClientError";
71
+ }
72
+ };
67
73
 
68
74
  // src/http.ts
69
75
  async function httpPost(config, path, body, headers) {
@@ -86,7 +92,7 @@ async function httpRequest(config, method, path, body, extraHeaders) {
86
92
  const fetchFn = config.fetch;
87
93
  const headers = {
88
94
  "X-Api-Key": config.apiKey,
89
- "User-Agent": "metrone-server-sdk/1.0.0",
95
+ "User-Agent": "metrone-server-sdk/1.0.2",
90
96
  ...extraHeaders
91
97
  };
92
98
  if (body !== void 0) {
@@ -120,6 +126,9 @@ async function httpRequest(config, method, path, body, extraHeaders) {
120
126
  throw new MetroneRateLimitError(retryMs);
121
127
  }
122
128
  if (response.status >= 500) throw new MetroneServerError(response.status, errMsg);
129
+ if (response.status >= 400 && response.status < 500) {
130
+ throw new MetroneClientError(response.status, errMsg);
131
+ }
123
132
  return {
124
133
  ok: false,
125
134
  status: response.status,
@@ -130,6 +139,7 @@ async function httpRequest(config, method, path, body, extraHeaders) {
130
139
  if (err instanceof MetroneAuthError) throw err;
131
140
  if (err instanceof MetroneRateLimitError) throw err;
132
141
  if (err instanceof MetroneServerError) throw err;
142
+ if (err instanceof MetroneClientError) throw err;
133
143
  if (err instanceof DOMException && err.name === "AbortError") {
134
144
  throw new MetroneTimeoutError(config.timeoutMs);
135
145
  }
@@ -420,12 +430,26 @@ var MetroneServer = class {
420
430
  }
421
431
  return { sent: accepted, failed: batch.length - accepted, errors: [] };
422
432
  } catch (err) {
433
+ const isPermanent = err instanceof MetroneAuthError || err instanceof MetroneClientError || err instanceof MetroneError && err.retryable === false;
434
+ if (isPermanent) {
435
+ if (this.config.debug) {
436
+ console.error(
437
+ `[metrone] Dropping ${batch.length} events \u2014 server rejected them permanently:`,
438
+ err instanceof Error ? err.message : err
439
+ );
440
+ }
441
+ return {
442
+ sent: 0,
443
+ failed: batch.length,
444
+ errors: [{ index: -1, error: err instanceof Error ? err.message : String(err) }]
445
+ };
446
+ }
423
447
  this.queue.unshift(...batch);
424
448
  if (this.queue.length > this.config.maxQueueSize) {
425
449
  this.queue.length = this.config.maxQueueSize;
426
450
  }
427
451
  if (this.config.debug) {
428
- console.error("[metrone] Flush failed, re-queued:", err);
452
+ console.error("[metrone] Flush failed (transient), re-queued:", err);
429
453
  }
430
454
  return {
431
455
  sent: 0,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["/**\n * Structured error types for the Metrone server SDK.\n *\n * All errors carry a machine-readable `code` that agents can switch on,\n * plus a human-readable `message` for logs.\n */\n\nexport class MetroneError extends Error {\n public readonly code: string\n public readonly status?: number\n public readonly retryable: boolean\n\n constructor(code: string, message: string, status?: number, retryable = false) {\n super(message)\n this.name = 'MetroneError'\n this.code = code\n this.status = status\n this.retryable = retryable\n }\n}\n\nexport class MetroneConfigError extends MetroneError {\n constructor(message: string) {\n super('CONFIG_INVALID', message)\n this.name = 'MetroneConfigError'\n }\n}\n\nexport class MetroneAuthError extends MetroneError {\n constructor(message = 'Invalid or missing API key') {\n super('AUTH_FAILED', message, 401, false)\n this.name = 'MetroneAuthError'\n }\n}\n\nexport class MetroneRateLimitError extends MetroneError {\n public readonly retryAfterMs: number\n\n constructor(retryAfterMs: number) {\n super('RATE_LIMITED', `Rate limit exceeded. Retry after ${retryAfterMs}ms.`, 429, true)\n this.name = 'MetroneRateLimitError'\n this.retryAfterMs = retryAfterMs\n }\n}\n\nexport class MetroneQuotaError extends MetroneError {\n constructor(message = 'Monthly event quota exceeded') {\n super('QUOTA_EXCEEDED', message, 429, false)\n this.name = 'MetroneQuotaError'\n }\n}\n\nexport class MetroneNetworkError extends MetroneError {\n constructor(message = 'Network request failed') {\n super('NETWORK_ERROR', message, undefined, true)\n this.name = 'MetroneNetworkError'\n }\n}\n\nexport class MetroneTimeoutError extends MetroneError {\n constructor(timeoutMs: number) {\n super('TIMEOUT', `Request timed out after ${timeoutMs}ms`, undefined, true)\n this.name = 'MetroneTimeoutError'\n }\n}\n\nexport class MetroneValidationError extends MetroneError {\n public readonly fields: Array<{ field: string; message: string }>\n\n constructor(fields: Array<{ field: string; message: string }>) {\n const summary = fields.map(f => `${f.field}: ${f.message}`).join('; ')\n super('VALIDATION_FAILED', `Validation failed: ${summary}`, 400, false)\n this.name = 'MetroneValidationError'\n this.fields = fields\n }\n}\n\nexport class MetroneServerError extends MetroneError {\n constructor(status: number, message = 'Internal server error') {\n super('SERVER_ERROR', message, status, true)\n this.name = 'MetroneServerError'\n }\n}\n","/**\n * Runtime-agnostic HTTP layer.\n *\n * Uses the global fetch() which is available in Node 18+, Deno, Bun,\n * Cloudflare Workers, and all modern edge runtimes. A custom fetch can\n * be injected via config for environments that need it.\n */\n\nimport type { ResolvedConfig, ApiResponse } from './types.js'\nimport {\n MetroneNetworkError,\n MetroneTimeoutError,\n MetroneAuthError,\n MetroneRateLimitError,\n MetroneServerError,\n} from './errors.js'\n\nexport async function httpPost(\n config: ResolvedConfig,\n path: string,\n body: unknown,\n headers?: Record<string, string>,\n): Promise<ApiResponse> {\n return httpRequest(config, 'POST', path, body, headers)\n}\n\nexport async function httpGet(\n config: ResolvedConfig,\n path: string,\n params?: Record<string, string | number | undefined>,\n headers?: Record<string, string>,\n): Promise<ApiResponse> {\n let url = path\n if (params) {\n const qs = new URLSearchParams()\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined) qs.set(k, String(v))\n }\n const str = qs.toString()\n if (str) url += `?${str}`\n }\n return httpRequest(config, 'GET', url, undefined, headers)\n}\n\nasync function httpRequest(\n config: ResolvedConfig,\n method: string,\n path: string,\n body?: unknown,\n extraHeaders?: Record<string, string>,\n): Promise<ApiResponse> {\n const url = `${config.endpoint}${path}`\n const fetchFn = config.fetch\n\n const headers: Record<string, string> = {\n 'X-Api-Key': config.apiKey,\n 'User-Agent': 'metrone-server-sdk/1.0.0',\n ...extraHeaders,\n }\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json'\n }\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), config.timeoutMs)\n\n try {\n const response = await fetchFn(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n })\n\n clearTimeout(timer)\n\n let data: unknown\n const ct = response.headers.get('content-type') ?? ''\n if (ct.includes('application/json')) {\n data = await response.json()\n } else {\n data = await response.text()\n }\n\n if (response.ok) {\n return { ok: true, status: response.status, data }\n }\n\n const errBody = data as Record<string, unknown> | undefined\n const errMsg = typeof errBody?.error === 'string'\n ? errBody.error\n : typeof errBody?.error === 'object' && errBody.error !== null\n ? (errBody.error as Record<string, unknown>).message as string ?? response.statusText\n : response.statusText\n\n if (response.status === 401) throw new MetroneAuthError(errMsg)\n if (response.status === 429) {\n const retryMs = typeof errBody?.retry_after_ms === 'number'\n ? errBody.retry_after_ms\n : 60_000\n throw new MetroneRateLimitError(retryMs)\n }\n if (response.status >= 500) throw new MetroneServerError(response.status, errMsg)\n\n return {\n ok: false,\n status: response.status,\n error: { code: 'API_ERROR', message: errMsg, details: errBody },\n }\n } catch (err) {\n clearTimeout(timer)\n\n if (err instanceof MetroneAuthError) throw err\n if (err instanceof MetroneRateLimitError) throw err\n if (err instanceof MetroneServerError) throw err\n\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw new MetroneTimeoutError(config.timeoutMs)\n }\n if (err instanceof TypeError) {\n throw new MetroneNetworkError(err.message)\n }\n\n throw new MetroneNetworkError(\n err instanceof Error ? err.message : 'Unknown network error',\n )\n }\n}\n\n/**\n * Retry wrapper with exponential backoff.\n * Retries on MetroneNetworkError, MetroneTimeoutError, MetroneServerError,\n * and MetroneRateLimitError. Stops on auth or validation errors.\n */\nexport async function withRetry<T>(\n config: ResolvedConfig,\n fn: () => Promise<T>,\n): Promise<T> {\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn()\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err))\n\n const isRetryable =\n err instanceof MetroneNetworkError ||\n err instanceof MetroneTimeoutError ||\n err instanceof MetroneServerError ||\n err instanceof MetroneRateLimitError\n\n if (!isRetryable || attempt >= config.maxRetries) throw err\n\n const baseDelay = err instanceof MetroneRateLimitError\n ? err.retryAfterMs\n : config.retryBaseMs * Math.pow(2, attempt)\n\n const jitter = Math.random() * baseDelay * 0.1\n const delay = baseDelay + jitter\n\n if (config.debug) {\n const errLabel = err instanceof Error ? err.message : String(err)\n console.error(\n `[metrone] Retry ${attempt + 1}/${config.maxRetries} after ${Math.round(delay)}ms: ${errLabel}`,\n )\n }\n\n await sleep(delay)\n }\n }\n\n throw lastError\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n","/**\n * MetroneServer — the main server-side SDK client.\n *\n * Zero dependencies. Uses global fetch (Node 18+, Deno, Bun, edge runtimes).\n * Runtime-agnostic: no DOM, no window, no navigator, no localStorage.\n *\n * Features:\n * - Event ingestion (single + batch) with automatic batching\n * - AI tracking (calls, chats, intents, sessions)\n * - Read API (stats, events, pages, sources, live)\n * - Retry with exponential backoff + jitter\n * - Idempotency key support\n * - Agent identity tracking\n * - Graceful shutdown with flush()\n */\n\nimport type {\n MetroneServerConfig,\n ResolvedConfig,\n EventPayload,\n AICallData,\n AIChatData,\n AIIntentData,\n AISessionData,\n ConversionData,\n StatsParams,\n EventsParams,\n PagesParams,\n SourcesParams,\n FlushResult,\n StatsResponse,\n EventsResponse,\n PagesResponse,\n SourcesResponse,\n LiveResponse,\n ApiResponse,\n} from './types.js'\nimport { MetroneConfigError, MetroneValidationError } from './errors.js'\nimport { httpPost, httpGet, withRetry } from './http.js'\n\nconst API_KEY_PATTERN = /^(metrone_(live|test)_|mk_(live|test)_)[a-f0-9]{32}$/\n\nexport class MetroneServer {\n private readonly config: ResolvedConfig\n private queue: Array<EventPayload & { api_key: string }> = []\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private flushing = false\n private destroyed = false\n\n constructor(config: MetroneServerConfig) {\n if (!config.apiKey) {\n throw new MetroneConfigError('apiKey is required')\n }\n if (!API_KEY_PATTERN.test(config.apiKey)) {\n throw new MetroneConfigError(\n 'Invalid apiKey format. Expected metrone_live_*, metrone_test_*, mk_live_*, or mk_test_* followed by 32 hex chars.',\n )\n }\n\n const endpoint = (config.endpoint ?? 'https://api.metrone.io').replace(/\\/+$/, '')\n\n this.config = {\n apiKey: config.apiKey,\n endpoint,\n batchSize: config.batchSize ?? 10,\n flushIntervalMs: config.flushIntervalMs ?? 5000,\n maxRetries: config.maxRetries ?? 3,\n retryBaseMs: config.retryBaseMs ?? 1000,\n timeoutMs: config.timeoutMs ?? 10_000,\n maxQueueSize: config.maxQueueSize ?? 1000,\n debug: config.debug ?? false,\n fetch: config.fetch ?? globalThis.fetch.bind(globalThis),\n }\n\n if (this.config.batchSize > 0 && this.config.flushIntervalMs > 0) {\n this.flushTimer = setInterval(() => {\n this.flush().catch(err => {\n if (this.config.debug) {\n console.error('[metrone] Auto-flush error:', err)\n }\n })\n }, this.config.flushIntervalMs)\n\n if (typeof this.flushTimer === 'object' && 'unref' in this.flushTimer) {\n (this.flushTimer as NodeJS.Timeout).unref()\n }\n }\n\n if (this.config.debug) {\n console.error(`[metrone] Server SDK initialized (endpoint: ${this.config.endpoint})`)\n }\n }\n\n // ─── Event Tracking ────────────────────────────────────────────────────────\n\n /**\n * Track a custom event. Queued for batch sending unless batchSize is 0.\n */\n track(eventType: string, data?: Partial<EventPayload>): void {\n this.assertNotDestroyed()\n if (!eventType) throw new MetroneValidationError([{ field: 'event_type', message: 'required' }])\n\n const event: EventPayload & { api_key: string } = {\n api_key: this.config.apiKey,\n event_type: eventType,\n timestamp: new Date().toISOString(),\n ...data,\n }\n\n if (this.config.batchSize <= 0) {\n withRetry(this.config, () => httpPost(this.config, '/v1/events', event)).catch(err => {\n if (this.config.debug) console.error('[metrone] Send error:', err)\n })\n return\n }\n\n this.queue.push(event)\n if (this.queue.length >= this.config.batchSize) {\n this.flush().catch(err => {\n if (this.config.debug) console.error('[metrone] Flush error:', err)\n })\n }\n }\n\n /**\n * Track a page view.\n */\n pageview(url: string, title?: string, data?: Partial<EventPayload>): void {\n this.track('pageview', {\n page_url: url,\n page_title: title,\n source: 'web',\n ...data,\n })\n }\n\n /**\n * Track a conversion event.\n */\n conversion(data: ConversionData): void {\n this.track('conversion', {\n source: 'web',\n properties: {\n conversion_type: data.conversion_type,\n value: data.value,\n currency: data.currency,\n ...data.properties,\n },\n })\n }\n\n // ─── AI Tracking ───────────────────────────────────────────────────────────\n\n /**\n * Track an AI voice call.\n */\n trackAICall(data: AICallData): void {\n this.track('ai_call', {\n source: 'voice',\n channel: 'phone',\n ai_provider: data.provider ?? 'unknown',\n ai_call_id: data.call_id,\n ai_intent: data.intent,\n ai_duration_sec: data.duration,\n session_id: data.session_id,\n properties: {\n transcript_snippet: data.transcript_snippet,\n outcome: data.outcome,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI chat interaction.\n */\n trackAIChat(data: AIChatData): void {\n this.track('ai_chat', {\n source: 'chat',\n channel: 'website',\n ai_provider: data.provider ?? 'unknown',\n ai_session_id: data.session_id,\n ai_intent: data.intent,\n ai_duration_sec: data.duration,\n properties: {\n message_count: data.message_count,\n resolved: data.resolved,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI intent detection.\n */\n trackAIIntent(data: AIIntentData): void {\n this.track('ai_intent', {\n source: data.source ?? 'assistant',\n ai_intent: data.intent,\n properties: {\n confidence: data.confidence,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI session lifecycle event (start, end, timeout).\n */\n trackAISession(data: AISessionData): void {\n this.track(`ai_session_${data.action}`, {\n source: 'assistant',\n ai_provider: data.provider ?? 'unknown',\n ai_session_id: data.session_id,\n ai_duration_sec: data.duration,\n properties: data.properties,\n })\n }\n\n // ─── Read API ──────────────────────────────────────────────────────────────\n\n /**\n * Get aggregated analytics stats for a time period.\n */\n async getStats(params?: StatsParams): Promise<StatsResponse> {\n this.assertNotDestroyed()\n const qp = this.buildDateParams(params)\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/stats', qp),\n )\n return res.data as StatsResponse\n }\n\n /**\n * Get individual analytics events with pagination and filters.\n */\n async getEvents(params?: EventsParams): Promise<EventsResponse> {\n this.assertNotDestroyed()\n const qp: Record<string, string | number | undefined> = {\n ...this.buildDateParams(params),\n limit: params?.limit,\n offset: params?.offset,\n event_type: params?.event_type,\n source: params?.source,\n }\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/events', qp),\n )\n return res.data as EventsResponse\n }\n\n /**\n * Get page analytics broken down by path.\n */\n async getPages(params?: PagesParams): Promise<PagesResponse> {\n this.assertNotDestroyed()\n const qp: Record<string, string | number | undefined> = {\n ...this.buildDateParams(params),\n limit: params?.limit,\n }\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/pages', qp),\n )\n return res.data as PagesResponse\n }\n\n /**\n * Get traffic source and referrer analytics.\n */\n async getSources(params?: SourcesParams): Promise<SourcesResponse> {\n this.assertNotDestroyed()\n const qp = this.buildDateParams(params)\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/sources', qp),\n )\n return res.data as SourcesResponse\n }\n\n /**\n * Get real-time live stats (active visitors, today's totals).\n */\n async getLive(): Promise<LiveResponse> {\n this.assertNotDestroyed()\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/live'),\n )\n return res.data as LiveResponse\n }\n\n // ─── Batch & Lifecycle ─────────────────────────────────────────────────────\n\n /**\n * Immediately flush all queued events. Returns the result.\n * Safe to call multiple times concurrently — only one flush runs at a time.\n */\n async flush(): Promise<FlushResult> {\n if (this.flushing || this.queue.length === 0) {\n return { sent: 0, failed: 0, errors: [] }\n }\n\n this.flushing = true\n const batch = this.queue.splice(0, 100)\n\n try {\n const res = await withRetry(this.config, () =>\n httpPost(this.config, '/v1/events/batch', batch),\n )\n\n const body = res.data as Record<string, unknown> | undefined\n const accepted = typeof body?.accepted === 'number' ? body.accepted : batch.length\n\n if (this.config.debug) {\n console.error(`[metrone] Flushed ${accepted}/${batch.length} events`)\n }\n\n return { sent: accepted, failed: batch.length - accepted, errors: [] }\n } catch (err) {\n this.queue.unshift(...batch)\n if (this.queue.length > this.config.maxQueueSize) {\n this.queue.length = this.config.maxQueueSize\n }\n\n if (this.config.debug) {\n console.error('[metrone] Flush failed, re-queued:', err)\n }\n\n return {\n sent: 0,\n failed: batch.length,\n errors: [{ index: -1, error: err instanceof Error ? err.message : String(err) }],\n }\n } finally {\n this.flushing = false\n\n if (this.queue.length >= this.config.batchSize && this.config.batchSize > 0) {\n this.flush().catch(() => {})\n }\n }\n }\n\n /**\n * Flush remaining events, stop the auto-flush timer, and mark the\n * client as destroyed. Subsequent calls will throw.\n */\n async shutdown(): Promise<FlushResult> {\n if (this.destroyed) return { sent: 0, failed: 0, errors: [] }\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n const result = await this.flush()\n this.destroyed = true\n\n if (this.config.debug) {\n console.error('[metrone] Server SDK shut down')\n }\n\n return result\n }\n\n /** Number of events currently in the send queue. */\n get queueSize(): number {\n return this.queue.length\n }\n\n /** Whether shutdown() has been called. */\n get isDestroyed(): boolean {\n return this.destroyed\n }\n\n // ─── Internal ──────────────────────────────────────────────────────────────\n\n private assertNotDestroyed(): void {\n if (this.destroyed) {\n throw new MetroneConfigError('Client has been shut down. Create a new instance.')\n }\n }\n\n private buildDateParams(\n params?: { days?: number; from?: string | Date; to?: string | Date },\n ): Record<string, string | number | undefined> {\n const result: Record<string, string | number | undefined> = {}\n if (params?.days) result.days = params.days\n if (params?.from) result.from = params.from instanceof Date ? params.from.toISOString() : params.from\n if (params?.to) result.to = params.to instanceof Date ? params.to.toISOString() : params.to\n return result\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["/**\n * Structured error types for the Metrone server SDK.\n *\n * All errors carry a machine-readable `code` that agents can switch on,\n * plus a human-readable `message` for logs.\n */\n\nexport class MetroneError extends Error {\n public readonly code: string\n public readonly status?: number\n public readonly retryable: boolean\n\n constructor(code: string, message: string, status?: number, retryable = false) {\n super(message)\n this.name = 'MetroneError'\n this.code = code\n this.status = status\n this.retryable = retryable\n }\n}\n\nexport class MetroneConfigError extends MetroneError {\n constructor(message: string) {\n super('CONFIG_INVALID', message)\n this.name = 'MetroneConfigError'\n }\n}\n\nexport class MetroneAuthError extends MetroneError {\n constructor(message = 'Invalid or missing API key') {\n super('AUTH_FAILED', message, 401, false)\n this.name = 'MetroneAuthError'\n }\n}\n\nexport class MetroneRateLimitError extends MetroneError {\n public readonly retryAfterMs: number\n\n constructor(retryAfterMs: number) {\n super('RATE_LIMITED', `Rate limit exceeded. Retry after ${retryAfterMs}ms.`, 429, true)\n this.name = 'MetroneRateLimitError'\n this.retryAfterMs = retryAfterMs\n }\n}\n\nexport class MetroneQuotaError extends MetroneError {\n constructor(message = 'Monthly event quota exceeded') {\n super('QUOTA_EXCEEDED', message, 429, false)\n this.name = 'MetroneQuotaError'\n }\n}\n\nexport class MetroneNetworkError extends MetroneError {\n constructor(message = 'Network request failed') {\n super('NETWORK_ERROR', message, undefined, true)\n this.name = 'MetroneNetworkError'\n }\n}\n\nexport class MetroneTimeoutError extends MetroneError {\n constructor(timeoutMs: number) {\n super('TIMEOUT', `Request timed out after ${timeoutMs}ms`, undefined, true)\n this.name = 'MetroneTimeoutError'\n }\n}\n\nexport class MetroneValidationError extends MetroneError {\n public readonly fields: Array<{ field: string; message: string }>\n\n constructor(fields: Array<{ field: string; message: string }>) {\n const summary = fields.map(f => `${f.field}: ${f.message}`).join('; ')\n super('VALIDATION_FAILED', `Validation failed: ${summary}`, 400, false)\n this.name = 'MetroneValidationError'\n this.fields = fields\n }\n}\n\nexport class MetroneServerError extends MetroneError {\n constructor(status: number, message = 'Internal server error') {\n super('SERVER_ERROR', message, status, true)\n this.name = 'MetroneServerError'\n }\n}\n\n/**\n * 4xx response that isn't one of the more specific subclasses (auth, quota,\n * validation, rate-limit). Always permanent — re-sending the same payload\n * will produce the same error, so callers must NOT requeue events that\n * triggered it. This breaks the retry-storm pattern where a permanent server\n * rejection (e.g. `BATCH_ALL_INVALID`) used to be re-queued every flush\n * interval forever.\n */\nexport class MetroneClientError extends MetroneError {\n constructor(status: number, message = 'Request rejected by server') {\n super('CLIENT_ERROR', message, status, false)\n this.name = 'MetroneClientError'\n }\n}\n","/**\n * Runtime-agnostic HTTP layer.\n *\n * Uses the global fetch() which is available in Node 18+, Deno, Bun,\n * Cloudflare Workers, and all modern edge runtimes. A custom fetch can\n * be injected via config for environments that need it.\n */\n\nimport type { ResolvedConfig, ApiResponse } from './types.js'\nimport {\n MetroneNetworkError,\n MetroneTimeoutError,\n MetroneAuthError,\n MetroneRateLimitError,\n MetroneServerError,\n MetroneClientError,\n} from './errors.js'\n\nexport async function httpPost(\n config: ResolvedConfig,\n path: string,\n body: unknown,\n headers?: Record<string, string>,\n): Promise<ApiResponse> {\n return httpRequest(config, 'POST', path, body, headers)\n}\n\nexport async function httpGet(\n config: ResolvedConfig,\n path: string,\n params?: Record<string, string | number | undefined>,\n headers?: Record<string, string>,\n): Promise<ApiResponse> {\n let url = path\n if (params) {\n const qs = new URLSearchParams()\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined) qs.set(k, String(v))\n }\n const str = qs.toString()\n if (str) url += `?${str}`\n }\n return httpRequest(config, 'GET', url, undefined, headers)\n}\n\nasync function httpRequest(\n config: ResolvedConfig,\n method: string,\n path: string,\n body?: unknown,\n extraHeaders?: Record<string, string>,\n): Promise<ApiResponse> {\n const url = `${config.endpoint}${path}`\n const fetchFn = config.fetch\n\n const headers: Record<string, string> = {\n 'X-Api-Key': config.apiKey,\n 'User-Agent': 'metrone-server-sdk/1.0.2',\n ...extraHeaders,\n }\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json'\n }\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), config.timeoutMs)\n\n try {\n const response = await fetchFn(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n })\n\n clearTimeout(timer)\n\n let data: unknown\n const ct = response.headers.get('content-type') ?? ''\n if (ct.includes('application/json')) {\n data = await response.json()\n } else {\n data = await response.text()\n }\n\n if (response.ok) {\n return { ok: true, status: response.status, data }\n }\n\n const errBody = data as Record<string, unknown> | undefined\n const errMsg = typeof errBody?.error === 'string'\n ? errBody.error\n : typeof errBody?.error === 'object' && errBody.error !== null\n ? (errBody.error as Record<string, unknown>).message as string ?? response.statusText\n : response.statusText\n\n if (response.status === 401) throw new MetroneAuthError(errMsg)\n if (response.status === 429) {\n const retryMs = typeof errBody?.retry_after_ms === 'number'\n ? errBody.retry_after_ms\n : 60_000\n throw new MetroneRateLimitError(retryMs)\n }\n if (response.status >= 500) throw new MetroneServerError(response.status, errMsg)\n // Any other 4xx (validation failure, quota exceeded as 4xx, etc.) is a\n // permanent rejection. Throw a typed error so `withRetry` doesn't loop\n // and `flush()` knows to drop the events instead of re-queueing them.\n if (response.status >= 400 && response.status < 500) {\n throw new MetroneClientError(response.status, errMsg)\n }\n\n return {\n ok: false,\n status: response.status,\n error: { code: 'API_ERROR', message: errMsg, details: errBody },\n }\n } catch (err) {\n clearTimeout(timer)\n\n if (err instanceof MetroneAuthError) throw err\n if (err instanceof MetroneRateLimitError) throw err\n if (err instanceof MetroneServerError) throw err\n if (err instanceof MetroneClientError) throw err\n\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw new MetroneTimeoutError(config.timeoutMs)\n }\n if (err instanceof TypeError) {\n throw new MetroneNetworkError(err.message)\n }\n\n throw new MetroneNetworkError(\n err instanceof Error ? err.message : 'Unknown network error',\n )\n }\n}\n\n/**\n * Retry wrapper with exponential backoff.\n * Retries on MetroneNetworkError, MetroneTimeoutError, MetroneServerError,\n * and MetroneRateLimitError. Stops on auth or validation errors.\n */\nexport async function withRetry<T>(\n config: ResolvedConfig,\n fn: () => Promise<T>,\n): Promise<T> {\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn()\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err))\n\n const isRetryable =\n err instanceof MetroneNetworkError ||\n err instanceof MetroneTimeoutError ||\n err instanceof MetroneServerError ||\n err instanceof MetroneRateLimitError\n\n if (!isRetryable || attempt >= config.maxRetries) throw err\n\n const baseDelay = err instanceof MetroneRateLimitError\n ? err.retryAfterMs\n : config.retryBaseMs * Math.pow(2, attempt)\n\n const jitter = Math.random() * baseDelay * 0.1\n const delay = baseDelay + jitter\n\n if (config.debug) {\n const errLabel = err instanceof Error ? err.message : String(err)\n console.error(\n `[metrone] Retry ${attempt + 1}/${config.maxRetries} after ${Math.round(delay)}ms: ${errLabel}`,\n )\n }\n\n await sleep(delay)\n }\n }\n\n throw lastError\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n","/**\n * MetroneServer — the main server-side SDK client.\n *\n * Zero dependencies. Uses global fetch (Node 18+, Deno, Bun, edge runtimes).\n * Runtime-agnostic: no DOM, no window, no navigator, no localStorage.\n *\n * Features:\n * - Event ingestion (single + batch) with automatic batching\n * - AI tracking (calls, chats, intents, sessions)\n * - Read API (stats, events, pages, sources, live)\n * - Retry with exponential backoff + jitter\n * - Idempotency key support\n * - Agent identity tracking\n * - Graceful shutdown with flush()\n */\n\nimport type {\n MetroneServerConfig,\n ResolvedConfig,\n EventPayload,\n AICallData,\n AIChatData,\n AIIntentData,\n AISessionData,\n ConversionData,\n StatsParams,\n EventsParams,\n PagesParams,\n SourcesParams,\n FlushResult,\n StatsResponse,\n EventsResponse,\n PagesResponse,\n SourcesResponse,\n LiveResponse,\n ApiResponse,\n} from './types.js'\nimport {\n MetroneConfigError,\n MetroneValidationError,\n MetroneClientError,\n MetroneAuthError,\n MetroneError,\n} from './errors.js'\nimport { httpPost, httpGet, withRetry } from './http.js'\n\nconst API_KEY_PATTERN = /^(metrone_(live|test)_|mk_(live|test)_)[a-f0-9]{32}$/\n\nexport class MetroneServer {\n private readonly config: ResolvedConfig\n private queue: Array<EventPayload & { api_key: string }> = []\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private flushing = false\n private destroyed = false\n\n constructor(config: MetroneServerConfig) {\n if (!config.apiKey) {\n throw new MetroneConfigError('apiKey is required')\n }\n if (!API_KEY_PATTERN.test(config.apiKey)) {\n throw new MetroneConfigError(\n 'Invalid apiKey format. Expected metrone_live_*, metrone_test_*, mk_live_*, or mk_test_* followed by 32 hex chars.',\n )\n }\n\n const endpoint = (config.endpoint ?? 'https://api.metrone.io').replace(/\\/+$/, '')\n\n this.config = {\n apiKey: config.apiKey,\n endpoint,\n batchSize: config.batchSize ?? 10,\n flushIntervalMs: config.flushIntervalMs ?? 5000,\n maxRetries: config.maxRetries ?? 3,\n retryBaseMs: config.retryBaseMs ?? 1000,\n timeoutMs: config.timeoutMs ?? 10_000,\n maxQueueSize: config.maxQueueSize ?? 1000,\n debug: config.debug ?? false,\n fetch: config.fetch ?? globalThis.fetch.bind(globalThis),\n }\n\n if (this.config.batchSize > 0 && this.config.flushIntervalMs > 0) {\n this.flushTimer = setInterval(() => {\n this.flush().catch(err => {\n if (this.config.debug) {\n console.error('[metrone] Auto-flush error:', err)\n }\n })\n }, this.config.flushIntervalMs)\n\n if (typeof this.flushTimer === 'object' && 'unref' in this.flushTimer) {\n (this.flushTimer as NodeJS.Timeout).unref()\n }\n }\n\n if (this.config.debug) {\n console.error(`[metrone] Server SDK initialized (endpoint: ${this.config.endpoint})`)\n }\n }\n\n // ─── Event Tracking ────────────────────────────────────────────────────────\n\n /**\n * Track a custom event. Queued for batch sending unless batchSize is 0.\n */\n track(eventType: string, data?: Partial<EventPayload>): void {\n this.assertNotDestroyed()\n if (!eventType) throw new MetroneValidationError([{ field: 'event_type', message: 'required' }])\n\n const event: EventPayload & { api_key: string } = {\n api_key: this.config.apiKey,\n event_type: eventType,\n timestamp: new Date().toISOString(),\n ...data,\n }\n\n if (this.config.batchSize <= 0) {\n withRetry(this.config, () => httpPost(this.config, '/v1/events', event)).catch(err => {\n if (this.config.debug) console.error('[metrone] Send error:', err)\n })\n return\n }\n\n this.queue.push(event)\n if (this.queue.length >= this.config.batchSize) {\n this.flush().catch(err => {\n if (this.config.debug) console.error('[metrone] Flush error:', err)\n })\n }\n }\n\n /**\n * Track a page view.\n */\n pageview(url: string, title?: string, data?: Partial<EventPayload>): void {\n this.track('pageview', {\n page_url: url,\n page_title: title,\n source: 'web',\n ...data,\n })\n }\n\n /**\n * Track a conversion event.\n */\n conversion(data: ConversionData): void {\n this.track('conversion', {\n source: 'web',\n properties: {\n conversion_type: data.conversion_type,\n value: data.value,\n currency: data.currency,\n ...data.properties,\n },\n })\n }\n\n // ─── AI Tracking ───────────────────────────────────────────────────────────\n\n /**\n * Track an AI voice call.\n */\n trackAICall(data: AICallData): void {\n this.track('ai_call', {\n source: 'voice',\n channel: 'phone',\n ai_provider: data.provider ?? 'unknown',\n ai_call_id: data.call_id,\n ai_intent: data.intent,\n ai_duration_sec: data.duration,\n session_id: data.session_id,\n properties: {\n transcript_snippet: data.transcript_snippet,\n outcome: data.outcome,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI chat interaction.\n */\n trackAIChat(data: AIChatData): void {\n this.track('ai_chat', {\n source: 'chat',\n channel: 'website',\n ai_provider: data.provider ?? 'unknown',\n ai_session_id: data.session_id,\n ai_intent: data.intent,\n ai_duration_sec: data.duration,\n properties: {\n message_count: data.message_count,\n resolved: data.resolved,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI intent detection.\n */\n trackAIIntent(data: AIIntentData): void {\n this.track('ai_intent', {\n source: data.source ?? 'assistant',\n ai_intent: data.intent,\n properties: {\n confidence: data.confidence,\n ...data.properties,\n },\n })\n }\n\n /**\n * Track an AI session lifecycle event (start, end, timeout).\n */\n trackAISession(data: AISessionData): void {\n this.track(`ai_session_${data.action}`, {\n source: 'assistant',\n ai_provider: data.provider ?? 'unknown',\n ai_session_id: data.session_id,\n ai_duration_sec: data.duration,\n properties: data.properties,\n })\n }\n\n // ─── Read API ──────────────────────────────────────────────────────────────\n\n /**\n * Get aggregated analytics stats for a time period.\n */\n async getStats(params?: StatsParams): Promise<StatsResponse> {\n this.assertNotDestroyed()\n const qp = this.buildDateParams(params)\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/stats', qp),\n )\n return res.data as StatsResponse\n }\n\n /**\n * Get individual analytics events with pagination and filters.\n */\n async getEvents(params?: EventsParams): Promise<EventsResponse> {\n this.assertNotDestroyed()\n const qp: Record<string, string | number | undefined> = {\n ...this.buildDateParams(params),\n limit: params?.limit,\n offset: params?.offset,\n event_type: params?.event_type,\n source: params?.source,\n }\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/events', qp),\n )\n return res.data as EventsResponse\n }\n\n /**\n * Get page analytics broken down by path.\n */\n async getPages(params?: PagesParams): Promise<PagesResponse> {\n this.assertNotDestroyed()\n const qp: Record<string, string | number | undefined> = {\n ...this.buildDateParams(params),\n limit: params?.limit,\n }\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/pages', qp),\n )\n return res.data as PagesResponse\n }\n\n /**\n * Get traffic source and referrer analytics.\n */\n async getSources(params?: SourcesParams): Promise<SourcesResponse> {\n this.assertNotDestroyed()\n const qp = this.buildDateParams(params)\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/sources', qp),\n )\n return res.data as SourcesResponse\n }\n\n /**\n * Get real-time live stats (active visitors, today's totals).\n */\n async getLive(): Promise<LiveResponse> {\n this.assertNotDestroyed()\n const res = await withRetry(this.config, () =>\n httpGet(this.config, '/v1/api/live'),\n )\n return res.data as LiveResponse\n }\n\n // ─── Batch & Lifecycle ─────────────────────────────────────────────────────\n\n /**\n * Immediately flush all queued events. Returns the result.\n * Safe to call multiple times concurrently — only one flush runs at a time.\n */\n async flush(): Promise<FlushResult> {\n if (this.flushing || this.queue.length === 0) {\n return { sent: 0, failed: 0, errors: [] }\n }\n\n this.flushing = true\n const batch = this.queue.splice(0, 100)\n\n try {\n const res = await withRetry(this.config, () =>\n httpPost(this.config, '/v1/events/batch', batch),\n )\n\n const body = res.data as Record<string, unknown> | undefined\n const accepted = typeof body?.accepted === 'number' ? body.accepted : batch.length\n\n if (this.config.debug) {\n console.error(`[metrone] Flushed ${accepted}/${batch.length} events`)\n }\n\n return { sent: accepted, failed: batch.length - accepted, errors: [] }\n } catch (err) {\n // Permanent failures (auth, validation, other 4xx) MUST NOT be\n // re-queued — re-sending the same payload will produce the same error,\n // turning every flushIntervalMs tick into a retry storm. Drop the\n // events and surface the failure to the caller instead.\n const isPermanent =\n err instanceof MetroneAuthError ||\n err instanceof MetroneClientError ||\n (err instanceof MetroneError && err.retryable === false)\n\n if (isPermanent) {\n if (this.config.debug) {\n console.error(\n `[metrone] Dropping ${batch.length} events — server rejected them permanently:`,\n err instanceof Error ? err.message : err,\n )\n }\n return {\n sent: 0,\n failed: batch.length,\n errors: [{ index: -1, error: err instanceof Error ? err.message : String(err) }],\n }\n }\n\n // Transient failures (network, timeout, 5xx, 429 after retries) — keep\n // events for the next flush attempt.\n this.queue.unshift(...batch)\n if (this.queue.length > this.config.maxQueueSize) {\n this.queue.length = this.config.maxQueueSize\n }\n\n if (this.config.debug) {\n console.error('[metrone] Flush failed (transient), re-queued:', err)\n }\n\n return {\n sent: 0,\n failed: batch.length,\n errors: [{ index: -1, error: err instanceof Error ? err.message : String(err) }],\n }\n } finally {\n this.flushing = false\n\n if (this.queue.length >= this.config.batchSize && this.config.batchSize > 0) {\n this.flush().catch(() => {})\n }\n }\n }\n\n /**\n * Flush remaining events, stop the auto-flush timer, and mark the\n * client as destroyed. Subsequent calls will throw.\n */\n async shutdown(): Promise<FlushResult> {\n if (this.destroyed) return { sent: 0, failed: 0, errors: [] }\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n const result = await this.flush()\n this.destroyed = true\n\n if (this.config.debug) {\n console.error('[metrone] Server SDK shut down')\n }\n\n return result\n }\n\n /** Number of events currently in the send queue. */\n get queueSize(): number {\n return this.queue.length\n }\n\n /** Whether shutdown() has been called. */\n get isDestroyed(): boolean {\n return this.destroyed\n }\n\n // ─── Internal ──────────────────────────────────────────────────────────────\n\n private assertNotDestroyed(): void {\n if (this.destroyed) {\n throw new MetroneConfigError('Client has been shut down. Create a new instance.')\n }\n }\n\n private buildDateParams(\n params?: { days?: number; from?: string | Date; to?: string | Date },\n ): Record<string, string | number | undefined> {\n const result: Record<string, string | number | undefined> = {}\n if (params?.days) result.days = params.days\n if (params?.from) result.from = params.from instanceof Date ? params.from.toISOString() : params.from\n if (params?.to) result.to = params.to instanceof Date ? params.to.toISOString() : params.to\n return result\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metrone-io/server",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Metrone server-side SDK for Node.js, Deno, Bun, and edge runtimes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -47,9 +47,13 @@
47
47
  "license": "MIT",
48
48
  "repository": {
49
49
  "type": "git",
50
- "url": "https://github.com/metrone/metrone.git",
50
+ "url": "https://github.com/metrone-io/sdks.git",
51
51
  "directory": "packages/server"
52
52
  },
53
+ "bugs": {
54
+ "url": "https://github.com/metrone-io/sdks/issues"
55
+ },
56
+ "homepage": "https://metrone.io",
53
57
  "engines": {
54
58
  "node": ">=18.0.0"
55
59
  },