@pingaura/telemetry 0.1.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-Cvg8zn6Q.mjs","names":[],"sources":["../src/contract.ts","../src/core.ts"],"sourcesContent":["// PingAura analytics event envelope. Zero runtime dependencies by design.\n\nexport const ANALYTICS_SCHEMA_VERSION = 1 as const;\n\nexport const EVENT_TYPES = [\n 'page_view',\n 'page_leave',\n 'web_vitals',\n 'track',\n] as const;\nexport type EventType = (typeof EVENT_TYPES)[number];\n\nexport interface EventContext {\n url: string;\n path?: string;\n referrer?: string;\n title?: string;\n locale?: string;\n screen?: string;\n user_agent?: string;\n country?: string;\n}\n\nexport interface AnalyticsEvent {\n event_id: string;\n schema_version: typeof ANALYTICS_SCHEMA_VERSION;\n type: EventType;\n view_id?: string;\n timestamp: string;\n sent_at: string;\n context: EventContext;\n properties: Record<string, unknown>;\n library?: { name: string; version: string };\n}\n\nexport interface BuildEventInput {\n type: EventType;\n context: EventContext;\n /**\n * Event metadata, archived verbatim. Never put PII here (emails, names, user\n * IDs, raw query strings) — the collector rejects events whose values look\n * like PII. Use opaque or aggregate values only.\n */\n properties?: Record<string, unknown>;\n timestamp?: string;\n view_id?: string;\n library?: { name: string; version: string };\n}\n\n/** Build a contract-valid event envelope. `now` is injectable for tests. */\nexport function buildEvent(\n input: BuildEventInput,\n now: () => string = () => new Date().toISOString(),\n): AnalyticsEvent {\n const ts = input.timestamp ?? now();\n return {\n event_id: globalThis.crypto.randomUUID(),\n schema_version: ANALYTICS_SCHEMA_VERSION,\n type: input.type,\n view_id: input.view_id,\n timestamp: ts,\n sent_at: now(),\n context: input.context,\n properties: input.properties ?? {},\n library: input.library,\n };\n}\n","import {\n type AnalyticsEvent,\n type BuildEventInput,\n buildEvent,\n} from './contract';\n\nexport interface ClientConfig {\n /** Ingest key (e.g. `process.env.PINGAURA_INGEST_KEY`). Missing/empty disables tracking. */\n writeKey: string | undefined;\n /** Ingest endpoint. Defaults to the production collector; override for region/dev/testing. */\n endpoint?: string;\n /** Registered site domain (e.g. \"example.com\"), required for attribution. Missing/empty disables tracking. */\n domain: string | undefined;\n timeoutMs?: number;\n debug?: boolean;\n /** Injectable for tests; defaults to global fetch. */\n fetchImpl?: typeof fetch;\n /** Injectable warning sink; defaults to console.warn. */\n onWarn?: (message: string) => void;\n /** Pass keepalive on the fetch (default true). Set false in edge runtimes (use ctx.waitUntil). */\n keepalive?: boolean;\n}\n\nexport interface SendOptions {\n /** Visitor IP — sent as the X-PA-Client-IP header, never in the body. */\n ip?: string;\n}\n\nexport interface PageViewInput {\n url: string;\n path?: string;\n referrer?: string;\n title?: string;\n locale?: string;\n userAgent?: string;\n ip?: string;\n properties?: Record<string, unknown>;\n}\n\nexport interface AnalyticsClient {\n send(event: AnalyticsEvent, options?: SendOptions): Promise<void>;\n sendRaw(input: BuildEventInput, options?: SendOptions): Promise<void>;\n pageView(input: PageViewInput): Promise<void>;\n /** `properties` is archived verbatim — never pass PII; opaque/aggregate values only. */\n track(\n name: string,\n properties: Record<string, unknown> | undefined,\n context: { url: string; path?: string; referrer?: string },\n ): Promise<void>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 2000;\nconst DEFAULT_ENDPOINT = 'https://telemetry.pingaura.ai/v1/events';\n\n// Reads a 2xx body (releasing the keepalive socket) and returns the collector's\n// rejected count. Cancels the stream on a non-JSON body so the socket is freed.\nasync function drainAndCountRejected(res: Response): Promise<number> {\n try {\n if (typeof res.json === 'function') {\n const body = (await res.json()) as { rejected?: unknown };\n const rejected = body?.rejected;\n if (Array.isArray(rejected)) return rejected.length;\n if (typeof rejected === 'number') return rejected;\n return 0;\n }\n } catch {\n // not JSON / already consumed — fall through to cancel\n }\n await res.body?.cancel?.().catch(() => {});\n return 0;\n}\n\nexport function createClient(config: ClientConfig): AnalyticsClient {\n const fetchImpl = config.fetchImpl ?? globalThis.fetch;\n const warn = config.onWarn ?? ((m: string) => console.warn(m));\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const endpoint = config.endpoint ?? DEFAULT_ENDPOINT;\n // 4xx rejections never self-heal — warn once per status instead of per request.\n const warnedStatuses = new Set<number>();\n\n if (!config.writeKey)\n warn('[pingaura] writeKey missing — analytics disabled');\n if (!config.domain) warn('[pingaura] domain missing — analytics disabled');\n\n async function send(\n event: AnalyticsEvent,\n options: SendOptions = {},\n ): Promise<void> {\n if (!config.writeKey || !config.domain) return; // no-op (already warned at init)\n\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n authorization: `Bearer ${config.writeKey}`,\n };\n if (options.ip) headers['x-pa-client-ip'] = options.ip;\n\n let body: string;\n try {\n body = JSON.stringify({ domain: config.domain, events: [event] });\n } catch (err) {\n if (config.debug)\n warn(`[pingaura] failed to serialize event: ${String(err)}`);\n return;\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetchImpl(endpoint, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n // best-effort; ignored on older Node and rejected by some edge runtimes for large bodies\n keepalive: config.keepalive ?? true,\n });\n\n if (res.status < 200 || res.status >= 300) {\n let detail = '';\n try {\n detail = ((await res.text?.()) ?? '').slice(0, 200);\n } catch {\n // body unreadable — the status code alone is enough\n }\n const message = `[pingaura] ingest rejected (${res.status})${detail ? `: ${detail}` : ''}`;\n if (res.status === 429 || res.status >= 500) {\n // transient (rate-limit / server) — debug-only, same as network failures\n if (config.debug) warn(message);\n } else if (!warnedStatuses.has(res.status)) {\n // deterministic client error (bad key/domain/payload) — warn once\n warnedStatuses.add(res.status);\n warn(message);\n }\n return;\n }\n\n // 2xx: drain the body to release the keepalive socket and surface partial drops\n const rejected = await drainAndCountRejected(res);\n if (rejected > 0)\n warn(`[pingaura] ${rejected} event(s) rejected by collector`);\n } catch (err) {\n // network / abort / timeout — transient; quiet outside debug\n if (config.debug) warn(`[pingaura] send failed: ${String(err)}`);\n } finally {\n clearTimeout(timer);\n }\n }\n\n // Build inside the guard so a synchronous throw (crypto.randomUUID) never\n // escapes into the caller's request path — every adapter is fire-and-forget.\n function safeSend(\n input: BuildEventInput,\n options?: SendOptions,\n ): Promise<void> {\n let event: AnalyticsEvent;\n try {\n event = buildEvent(input);\n } catch (err) {\n if (config.debug)\n warn(`[pingaura] failed to build event: ${String(err)}`);\n return Promise.resolve();\n }\n return send(event, options);\n }\n\n return {\n send,\n sendRaw: (input, options) => safeSend(input, options),\n pageView: (input) =>\n safeSend(\n {\n type: 'page_view',\n context: {\n url: input.url,\n path: input.path,\n referrer: input.referrer,\n title: input.title,\n locale: input.locale,\n user_agent: input.userAgent,\n },\n properties: input.properties,\n },\n { ip: input.ip },\n ),\n track: (name, properties = {}, context) =>\n safeSend({\n type: 'track',\n context: {\n url: context.url,\n path: context.path,\n referrer: context.referrer,\n },\n properties: { ...properties, name },\n }),\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ //#region src/contract.d.ts
2
+ declare const ANALYTICS_SCHEMA_VERSION: 1;
3
+ declare const EVENT_TYPES: readonly ["page_view", "page_leave", "web_vitals", "track"];
4
+ type EventType = (typeof EVENT_TYPES)[number];
5
+ interface EventContext {
6
+ url: string;
7
+ path?: string;
8
+ referrer?: string;
9
+ title?: string;
10
+ locale?: string;
11
+ screen?: string;
12
+ user_agent?: string;
13
+ country?: string;
14
+ }
15
+ interface AnalyticsEvent {
16
+ event_id: string;
17
+ schema_version: typeof ANALYTICS_SCHEMA_VERSION;
18
+ type: EventType;
19
+ view_id?: string;
20
+ timestamp: string;
21
+ sent_at: string;
22
+ context: EventContext;
23
+ properties: Record<string, unknown>;
24
+ library?: {
25
+ name: string;
26
+ version: string;
27
+ };
28
+ }
29
+ interface BuildEventInput {
30
+ type: EventType;
31
+ context: EventContext;
32
+ /**
33
+ * Event metadata, archived verbatim. Never put PII here (emails, names, user
34
+ * IDs, raw query strings) — the collector rejects events whose values look
35
+ * like PII. Use opaque or aggregate values only.
36
+ */
37
+ properties?: Record<string, unknown>;
38
+ timestamp?: string;
39
+ view_id?: string;
40
+ library?: {
41
+ name: string;
42
+ version: string;
43
+ };
44
+ }
45
+ /** Build a contract-valid event envelope. `now` is injectable for tests. */
46
+ declare function buildEvent(input: BuildEventInput, now?: () => string): AnalyticsEvent;
47
+ //#endregion
48
+ //#region src/core.d.ts
49
+ interface ClientConfig {
50
+ /** Ingest key (e.g. `process.env.PINGAURA_INGEST_KEY`). Missing/empty disables tracking. */
51
+ writeKey: string | undefined;
52
+ /** Ingest endpoint. Defaults to the production collector; override for region/dev/testing. */
53
+ endpoint?: string;
54
+ /** Registered site domain (e.g. "example.com"), required for attribution. Missing/empty disables tracking. */
55
+ domain: string | undefined;
56
+ timeoutMs?: number;
57
+ debug?: boolean;
58
+ /** Injectable for tests; defaults to global fetch. */
59
+ fetchImpl?: typeof fetch;
60
+ /** Injectable warning sink; defaults to console.warn. */
61
+ onWarn?: (message: string) => void;
62
+ /** Pass keepalive on the fetch (default true). Set false in edge runtimes (use ctx.waitUntil). */
63
+ keepalive?: boolean;
64
+ }
65
+ interface SendOptions {
66
+ /** Visitor IP — sent as the X-PA-Client-IP header, never in the body. */
67
+ ip?: string;
68
+ }
69
+ interface PageViewInput {
70
+ url: string;
71
+ path?: string;
72
+ referrer?: string;
73
+ title?: string;
74
+ locale?: string;
75
+ userAgent?: string;
76
+ ip?: string;
77
+ properties?: Record<string, unknown>;
78
+ }
79
+ interface AnalyticsClient {
80
+ send(event: AnalyticsEvent, options?: SendOptions): Promise<void>;
81
+ sendRaw(input: BuildEventInput, options?: SendOptions): Promise<void>;
82
+ pageView(input: PageViewInput): Promise<void>;
83
+ /** `properties` is archived verbatim — never pass PII; opaque/aggregate values only. */
84
+ track(name: string, properties: Record<string, unknown> | undefined, context: {
85
+ url: string;
86
+ path?: string;
87
+ referrer?: string;
88
+ }): Promise<void>;
89
+ }
90
+ declare function createClient(config: ClientConfig): AnalyticsClient;
91
+ //#endregion
92
+ export { createClient as a, BuildEventInput as c, EventType as d, buildEvent as f, SendOptions as i, EVENT_TYPES as l, ClientConfig as n, ANALYTICS_SCHEMA_VERSION as o, PageViewInput as r, AnalyticsEvent as s, AnalyticsClient as t, EventContext as u };
93
+ //# sourceMappingURL=core-DVilpn_i.d.cts.map
@@ -0,0 +1,93 @@
1
+ //#region src/contract.d.ts
2
+ declare const ANALYTICS_SCHEMA_VERSION: 1;
3
+ declare const EVENT_TYPES: readonly ["page_view", "page_leave", "web_vitals", "track"];
4
+ type EventType = (typeof EVENT_TYPES)[number];
5
+ interface EventContext {
6
+ url: string;
7
+ path?: string;
8
+ referrer?: string;
9
+ title?: string;
10
+ locale?: string;
11
+ screen?: string;
12
+ user_agent?: string;
13
+ country?: string;
14
+ }
15
+ interface AnalyticsEvent {
16
+ event_id: string;
17
+ schema_version: typeof ANALYTICS_SCHEMA_VERSION;
18
+ type: EventType;
19
+ view_id?: string;
20
+ timestamp: string;
21
+ sent_at: string;
22
+ context: EventContext;
23
+ properties: Record<string, unknown>;
24
+ library?: {
25
+ name: string;
26
+ version: string;
27
+ };
28
+ }
29
+ interface BuildEventInput {
30
+ type: EventType;
31
+ context: EventContext;
32
+ /**
33
+ * Event metadata, archived verbatim. Never put PII here (emails, names, user
34
+ * IDs, raw query strings) — the collector rejects events whose values look
35
+ * like PII. Use opaque or aggregate values only.
36
+ */
37
+ properties?: Record<string, unknown>;
38
+ timestamp?: string;
39
+ view_id?: string;
40
+ library?: {
41
+ name: string;
42
+ version: string;
43
+ };
44
+ }
45
+ /** Build a contract-valid event envelope. `now` is injectable for tests. */
46
+ declare function buildEvent(input: BuildEventInput, now?: () => string): AnalyticsEvent;
47
+ //#endregion
48
+ //#region src/core.d.ts
49
+ interface ClientConfig {
50
+ /** Ingest key (e.g. `process.env.PINGAURA_INGEST_KEY`). Missing/empty disables tracking. */
51
+ writeKey: string | undefined;
52
+ /** Ingest endpoint. Defaults to the production collector; override for region/dev/testing. */
53
+ endpoint?: string;
54
+ /** Registered site domain (e.g. "example.com"), required for attribution. Missing/empty disables tracking. */
55
+ domain: string | undefined;
56
+ timeoutMs?: number;
57
+ debug?: boolean;
58
+ /** Injectable for tests; defaults to global fetch. */
59
+ fetchImpl?: typeof fetch;
60
+ /** Injectable warning sink; defaults to console.warn. */
61
+ onWarn?: (message: string) => void;
62
+ /** Pass keepalive on the fetch (default true). Set false in edge runtimes (use ctx.waitUntil). */
63
+ keepalive?: boolean;
64
+ }
65
+ interface SendOptions {
66
+ /** Visitor IP — sent as the X-PA-Client-IP header, never in the body. */
67
+ ip?: string;
68
+ }
69
+ interface PageViewInput {
70
+ url: string;
71
+ path?: string;
72
+ referrer?: string;
73
+ title?: string;
74
+ locale?: string;
75
+ userAgent?: string;
76
+ ip?: string;
77
+ properties?: Record<string, unknown>;
78
+ }
79
+ interface AnalyticsClient {
80
+ send(event: AnalyticsEvent, options?: SendOptions): Promise<void>;
81
+ sendRaw(input: BuildEventInput, options?: SendOptions): Promise<void>;
82
+ pageView(input: PageViewInput): Promise<void>;
83
+ /** `properties` is archived verbatim — never pass PII; opaque/aggregate values only. */
84
+ track(name: string, properties: Record<string, unknown> | undefined, context: {
85
+ url: string;
86
+ path?: string;
87
+ referrer?: string;
88
+ }): Promise<void>;
89
+ }
90
+ declare function createClient(config: ClientConfig): AnalyticsClient;
91
+ //#endregion
92
+ export { createClient as a, BuildEventInput as c, EventType as d, buildEvent as f, SendOptions as i, EVENT_TYPES as l, ClientConfig as n, ANALYTICS_SCHEMA_VERSION as o, PageViewInput as r, AnalyticsEvent as s, AnalyticsClient as t, EventContext as u };
93
+ //# sourceMappingURL=core-DVilpn_i.d.mts.map
@@ -0,0 +1,162 @@
1
+ //#region src/contract.ts
2
+ const ANALYTICS_SCHEMA_VERSION = 1;
3
+ const EVENT_TYPES = [
4
+ "page_view",
5
+ "page_leave",
6
+ "web_vitals",
7
+ "track"
8
+ ];
9
+ /** Build a contract-valid event envelope. `now` is injectable for tests. */
10
+ function buildEvent(input, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
11
+ const ts = input.timestamp ?? now();
12
+ return {
13
+ event_id: globalThis.crypto.randomUUID(),
14
+ schema_version: 1,
15
+ type: input.type,
16
+ view_id: input.view_id,
17
+ timestamp: ts,
18
+ sent_at: now(),
19
+ context: input.context,
20
+ properties: input.properties ?? {},
21
+ library: input.library
22
+ };
23
+ }
24
+ //#endregion
25
+ //#region src/core.ts
26
+ const DEFAULT_TIMEOUT_MS = 2e3;
27
+ const DEFAULT_ENDPOINT = "https://telemetry.pingaura.ai/v1/events";
28
+ async function drainAndCountRejected(res) {
29
+ try {
30
+ if (typeof res.json === "function") {
31
+ const rejected = (await res.json())?.rejected;
32
+ if (Array.isArray(rejected)) return rejected.length;
33
+ if (typeof rejected === "number") return rejected;
34
+ return 0;
35
+ }
36
+ } catch {}
37
+ await res.body?.cancel?.().catch(() => {});
38
+ return 0;
39
+ }
40
+ function createClient(config) {
41
+ const fetchImpl = config.fetchImpl ?? globalThis.fetch;
42
+ const warn = config.onWarn ?? ((m) => console.warn(m));
43
+ const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
44
+ const endpoint = config.endpoint ?? DEFAULT_ENDPOINT;
45
+ const warnedStatuses = /* @__PURE__ */ new Set();
46
+ if (!config.writeKey) warn("[pingaura] writeKey missing — analytics disabled");
47
+ if (!config.domain) warn("[pingaura] domain missing — analytics disabled");
48
+ async function send(event, options = {}) {
49
+ if (!config.writeKey || !config.domain) return;
50
+ const headers = {
51
+ "content-type": "application/json",
52
+ authorization: `Bearer ${config.writeKey}`
53
+ };
54
+ if (options.ip) headers["x-pa-client-ip"] = options.ip;
55
+ let body;
56
+ try {
57
+ body = JSON.stringify({
58
+ domain: config.domain,
59
+ events: [event]
60
+ });
61
+ } catch (err) {
62
+ if (config.debug) warn(`[pingaura] failed to serialize event: ${String(err)}`);
63
+ return;
64
+ }
65
+ const controller = new AbortController();
66
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
67
+ try {
68
+ const res = await fetchImpl(endpoint, {
69
+ method: "POST",
70
+ headers,
71
+ body,
72
+ signal: controller.signal,
73
+ keepalive: config.keepalive ?? true
74
+ });
75
+ if (res.status < 200 || res.status >= 300) {
76
+ let detail = "";
77
+ try {
78
+ detail = (await res.text?.() ?? "").slice(0, 200);
79
+ } catch {}
80
+ const message = `[pingaura] ingest rejected (${res.status})${detail ? `: ${detail}` : ""}`;
81
+ if (res.status === 429 || res.status >= 500) {
82
+ if (config.debug) warn(message);
83
+ } else if (!warnedStatuses.has(res.status)) {
84
+ warnedStatuses.add(res.status);
85
+ warn(message);
86
+ }
87
+ return;
88
+ }
89
+ const rejected = await drainAndCountRejected(res);
90
+ if (rejected > 0) warn(`[pingaura] ${rejected} event(s) rejected by collector`);
91
+ } catch (err) {
92
+ if (config.debug) warn(`[pingaura] send failed: ${String(err)}`);
93
+ } finally {
94
+ clearTimeout(timer);
95
+ }
96
+ }
97
+ function safeSend(input, options) {
98
+ let event;
99
+ try {
100
+ event = buildEvent(input);
101
+ } catch (err) {
102
+ if (config.debug) warn(`[pingaura] failed to build event: ${String(err)}`);
103
+ return Promise.resolve();
104
+ }
105
+ return send(event, options);
106
+ }
107
+ return {
108
+ send,
109
+ sendRaw: (input, options) => safeSend(input, options),
110
+ pageView: (input) => safeSend({
111
+ type: "page_view",
112
+ context: {
113
+ url: input.url,
114
+ path: input.path,
115
+ referrer: input.referrer,
116
+ title: input.title,
117
+ locale: input.locale,
118
+ user_agent: input.userAgent
119
+ },
120
+ properties: input.properties
121
+ }, { ip: input.ip }),
122
+ track: (name, properties = {}, context) => safeSend({
123
+ type: "track",
124
+ context: {
125
+ url: context.url,
126
+ path: context.path,
127
+ referrer: context.referrer
128
+ },
129
+ properties: {
130
+ ...properties,
131
+ name
132
+ }
133
+ })
134
+ };
135
+ }
136
+ //#endregion
137
+ Object.defineProperty(exports, "ANALYTICS_SCHEMA_VERSION", {
138
+ enumerable: true,
139
+ get: function() {
140
+ return ANALYTICS_SCHEMA_VERSION;
141
+ }
142
+ });
143
+ Object.defineProperty(exports, "EVENT_TYPES", {
144
+ enumerable: true,
145
+ get: function() {
146
+ return EVENT_TYPES;
147
+ }
148
+ });
149
+ Object.defineProperty(exports, "buildEvent", {
150
+ enumerable: true,
151
+ get: function() {
152
+ return buildEvent;
153
+ }
154
+ });
155
+ Object.defineProperty(exports, "createClient", {
156
+ enumerable: true,
157
+ get: function() {
158
+ return createClient;
159
+ }
160
+ });
161
+
162
+ //# sourceMappingURL=core-KgLaVgR4.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-KgLaVgR4.cjs","names":[],"sources":["../src/contract.ts","../src/core.ts"],"sourcesContent":["// PingAura analytics event envelope. Zero runtime dependencies by design.\n\nexport const ANALYTICS_SCHEMA_VERSION = 1 as const;\n\nexport const EVENT_TYPES = [\n 'page_view',\n 'page_leave',\n 'web_vitals',\n 'track',\n] as const;\nexport type EventType = (typeof EVENT_TYPES)[number];\n\nexport interface EventContext {\n url: string;\n path?: string;\n referrer?: string;\n title?: string;\n locale?: string;\n screen?: string;\n user_agent?: string;\n country?: string;\n}\n\nexport interface AnalyticsEvent {\n event_id: string;\n schema_version: typeof ANALYTICS_SCHEMA_VERSION;\n type: EventType;\n view_id?: string;\n timestamp: string;\n sent_at: string;\n context: EventContext;\n properties: Record<string, unknown>;\n library?: { name: string; version: string };\n}\n\nexport interface BuildEventInput {\n type: EventType;\n context: EventContext;\n /**\n * Event metadata, archived verbatim. Never put PII here (emails, names, user\n * IDs, raw query strings) — the collector rejects events whose values look\n * like PII. Use opaque or aggregate values only.\n */\n properties?: Record<string, unknown>;\n timestamp?: string;\n view_id?: string;\n library?: { name: string; version: string };\n}\n\n/** Build a contract-valid event envelope. `now` is injectable for tests. */\nexport function buildEvent(\n input: BuildEventInput,\n now: () => string = () => new Date().toISOString(),\n): AnalyticsEvent {\n const ts = input.timestamp ?? now();\n return {\n event_id: globalThis.crypto.randomUUID(),\n schema_version: ANALYTICS_SCHEMA_VERSION,\n type: input.type,\n view_id: input.view_id,\n timestamp: ts,\n sent_at: now(),\n context: input.context,\n properties: input.properties ?? {},\n library: input.library,\n };\n}\n","import {\n type AnalyticsEvent,\n type BuildEventInput,\n buildEvent,\n} from './contract';\n\nexport interface ClientConfig {\n /** Ingest key (e.g. `process.env.PINGAURA_INGEST_KEY`). Missing/empty disables tracking. */\n writeKey: string | undefined;\n /** Ingest endpoint. Defaults to the production collector; override for region/dev/testing. */\n endpoint?: string;\n /** Registered site domain (e.g. \"example.com\"), required for attribution. Missing/empty disables tracking. */\n domain: string | undefined;\n timeoutMs?: number;\n debug?: boolean;\n /** Injectable for tests; defaults to global fetch. */\n fetchImpl?: typeof fetch;\n /** Injectable warning sink; defaults to console.warn. */\n onWarn?: (message: string) => void;\n /** Pass keepalive on the fetch (default true). Set false in edge runtimes (use ctx.waitUntil). */\n keepalive?: boolean;\n}\n\nexport interface SendOptions {\n /** Visitor IP — sent as the X-PA-Client-IP header, never in the body. */\n ip?: string;\n}\n\nexport interface PageViewInput {\n url: string;\n path?: string;\n referrer?: string;\n title?: string;\n locale?: string;\n userAgent?: string;\n ip?: string;\n properties?: Record<string, unknown>;\n}\n\nexport interface AnalyticsClient {\n send(event: AnalyticsEvent, options?: SendOptions): Promise<void>;\n sendRaw(input: BuildEventInput, options?: SendOptions): Promise<void>;\n pageView(input: PageViewInput): Promise<void>;\n /** `properties` is archived verbatim — never pass PII; opaque/aggregate values only. */\n track(\n name: string,\n properties: Record<string, unknown> | undefined,\n context: { url: string; path?: string; referrer?: string },\n ): Promise<void>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 2000;\nconst DEFAULT_ENDPOINT = 'https://telemetry.pingaura.ai/v1/events';\n\n// Reads a 2xx body (releasing the keepalive socket) and returns the collector's\n// rejected count. Cancels the stream on a non-JSON body so the socket is freed.\nasync function drainAndCountRejected(res: Response): Promise<number> {\n try {\n if (typeof res.json === 'function') {\n const body = (await res.json()) as { rejected?: unknown };\n const rejected = body?.rejected;\n if (Array.isArray(rejected)) return rejected.length;\n if (typeof rejected === 'number') return rejected;\n return 0;\n }\n } catch {\n // not JSON / already consumed — fall through to cancel\n }\n await res.body?.cancel?.().catch(() => {});\n return 0;\n}\n\nexport function createClient(config: ClientConfig): AnalyticsClient {\n const fetchImpl = config.fetchImpl ?? globalThis.fetch;\n const warn = config.onWarn ?? ((m: string) => console.warn(m));\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const endpoint = config.endpoint ?? DEFAULT_ENDPOINT;\n // 4xx rejections never self-heal — warn once per status instead of per request.\n const warnedStatuses = new Set<number>();\n\n if (!config.writeKey)\n warn('[pingaura] writeKey missing — analytics disabled');\n if (!config.domain) warn('[pingaura] domain missing — analytics disabled');\n\n async function send(\n event: AnalyticsEvent,\n options: SendOptions = {},\n ): Promise<void> {\n if (!config.writeKey || !config.domain) return; // no-op (already warned at init)\n\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n authorization: `Bearer ${config.writeKey}`,\n };\n if (options.ip) headers['x-pa-client-ip'] = options.ip;\n\n let body: string;\n try {\n body = JSON.stringify({ domain: config.domain, events: [event] });\n } catch (err) {\n if (config.debug)\n warn(`[pingaura] failed to serialize event: ${String(err)}`);\n return;\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetchImpl(endpoint, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n // best-effort; ignored on older Node and rejected by some edge runtimes for large bodies\n keepalive: config.keepalive ?? true,\n });\n\n if (res.status < 200 || res.status >= 300) {\n let detail = '';\n try {\n detail = ((await res.text?.()) ?? '').slice(0, 200);\n } catch {\n // body unreadable — the status code alone is enough\n }\n const message = `[pingaura] ingest rejected (${res.status})${detail ? `: ${detail}` : ''}`;\n if (res.status === 429 || res.status >= 500) {\n // transient (rate-limit / server) — debug-only, same as network failures\n if (config.debug) warn(message);\n } else if (!warnedStatuses.has(res.status)) {\n // deterministic client error (bad key/domain/payload) — warn once\n warnedStatuses.add(res.status);\n warn(message);\n }\n return;\n }\n\n // 2xx: drain the body to release the keepalive socket and surface partial drops\n const rejected = await drainAndCountRejected(res);\n if (rejected > 0)\n warn(`[pingaura] ${rejected} event(s) rejected by collector`);\n } catch (err) {\n // network / abort / timeout — transient; quiet outside debug\n if (config.debug) warn(`[pingaura] send failed: ${String(err)}`);\n } finally {\n clearTimeout(timer);\n }\n }\n\n // Build inside the guard so a synchronous throw (crypto.randomUUID) never\n // escapes into the caller's request path — every adapter is fire-and-forget.\n function safeSend(\n input: BuildEventInput,\n options?: SendOptions,\n ): Promise<void> {\n let event: AnalyticsEvent;\n try {\n event = buildEvent(input);\n } catch (err) {\n if (config.debug)\n warn(`[pingaura] failed to build event: ${String(err)}`);\n return Promise.resolve();\n }\n return send(event, options);\n }\n\n return {\n send,\n sendRaw: (input, options) => safeSend(input, options),\n pageView: (input) =>\n safeSend(\n {\n type: 'page_view',\n context: {\n url: input.url,\n path: input.path,\n referrer: input.referrer,\n title: input.title,\n locale: input.locale,\n user_agent: input.userAgent,\n },\n properties: input.properties,\n },\n { ip: input.ip },\n ),\n track: (name, properties = {}, context) =>\n safeSend({\n type: 'track',\n context: {\n url: context.url,\n path: context.path,\n referrer: context.referrer,\n },\n properties: { ...properties, name },\n }),\n };\n}\n"],"mappings":"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"}
package/dist/index.cjs ADDED
@@ -0,0 +1,6 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_core = require("./core-KgLaVgR4.cjs");
3
+ exports.ANALYTICS_SCHEMA_VERSION = require_core.ANALYTICS_SCHEMA_VERSION;
4
+ exports.EVENT_TYPES = require_core.EVENT_TYPES;
5
+ exports.buildEvent = require_core.buildEvent;
6
+ exports.createClient = require_core.createClient;
@@ -0,0 +1,2 @@
1
+ import { a as createClient, c as BuildEventInput, d as EventType, f as buildEvent, i as SendOptions, l as EVENT_TYPES, n as ClientConfig, o as ANALYTICS_SCHEMA_VERSION, r as PageViewInput, s as AnalyticsEvent, t as AnalyticsClient, u as EventContext } from "./core-DVilpn_i.cjs";
2
+ export { ANALYTICS_SCHEMA_VERSION, AnalyticsClient, AnalyticsEvent, BuildEventInput, ClientConfig, EVENT_TYPES, EventContext, EventType, PageViewInput, SendOptions, buildEvent, createClient };
@@ -0,0 +1,2 @@
1
+ import { a as createClient, c as BuildEventInput, d as EventType, f as buildEvent, i as SendOptions, l as EVENT_TYPES, n as ClientConfig, o as ANALYTICS_SCHEMA_VERSION, r as PageViewInput, s as AnalyticsEvent, t as AnalyticsClient, u as EventContext } from "./core-DVilpn_i.mjs";
2
+ export { ANALYTICS_SCHEMA_VERSION, AnalyticsClient, AnalyticsEvent, BuildEventInput, ClientConfig, EVENT_TYPES, EventContext, EventType, PageViewInput, SendOptions, buildEvent, createClient };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { i as buildEvent, n as ANALYTICS_SCHEMA_VERSION, r as EVENT_TYPES, t as createClient } from "./core-Cvg8zn6Q.mjs";
2
+ export { ANALYTICS_SCHEMA_VERSION, EVENT_TYPES, buildEvent, createClient };
@@ -0,0 +1,18 @@
1
+ //#region src/matchers.ts
2
+ const ASSET_EXT = /\.(?:ico|png|jpe?g|gif|svg|webp|avif|css|js|map|txt|xml|json|woff2?|ttf|eot|mp4|webm)$/i;
3
+ /** Skip framework internals, API routes, and static assets. */
4
+ function shouldTrackPath(pathname) {
5
+ if (pathname.startsWith("/_next/")) return false;
6
+ if (pathname.startsWith("/api/")) return false;
7
+ if (ASSET_EXT.test(pathname)) return false;
8
+ return true;
9
+ }
10
+ //#endregion
11
+ Object.defineProperty(exports, "shouldTrackPath", {
12
+ enumerable: true,
13
+ get: function() {
14
+ return shouldTrackPath;
15
+ }
16
+ });
17
+
18
+ //# sourceMappingURL=matchers-CMwG37qc.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers-CMwG37qc.cjs","names":[],"sources":["../src/matchers.ts"],"sourcesContent":["// Default path matcher shared by the next/node/cloudflare adapters.\n\nconst ASSET_EXT =\n /\\.(?:ico|png|jpe?g|gif|svg|webp|avif|css|js|map|txt|xml|json|woff2?|ttf|eot|mp4|webm)$/i;\n\n/** Skip framework internals, API routes, and static assets. */\nexport function shouldTrackPath(pathname: string): boolean {\n if (pathname.startsWith('/_next/')) return false;\n if (pathname.startsWith('/api/')) return false;\n if (ASSET_EXT.test(pathname)) return false;\n return true;\n}\n"],"mappings":";AAEA,MAAM,YACJ;;AAGF,SAAgB,gBAAgB,UAA2B;CACzD,IAAI,SAAS,WAAW,SAAS,GAAG,OAAO;CAC3C,IAAI,SAAS,WAAW,OAAO,GAAG,OAAO;CACzC,IAAI,UAAU,KAAK,QAAQ,GAAG,OAAO;CACrC,OAAO;AACT"}
@@ -0,0 +1,13 @@
1
+ //#region src/matchers.ts
2
+ const ASSET_EXT = /\.(?:ico|png|jpe?g|gif|svg|webp|avif|css|js|map|txt|xml|json|woff2?|ttf|eot|mp4|webm)$/i;
3
+ /** Skip framework internals, API routes, and static assets. */
4
+ function shouldTrackPath(pathname) {
5
+ if (pathname.startsWith("/_next/")) return false;
6
+ if (pathname.startsWith("/api/")) return false;
7
+ if (ASSET_EXT.test(pathname)) return false;
8
+ return true;
9
+ }
10
+ //#endregion
11
+ export { shouldTrackPath as t };
12
+
13
+ //# sourceMappingURL=matchers-CUD5NL5K.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers-CUD5NL5K.mjs","names":[],"sources":["../src/matchers.ts"],"sourcesContent":["// Default path matcher shared by the next/node/cloudflare adapters.\n\nconst ASSET_EXT =\n /\\.(?:ico|png|jpe?g|gif|svg|webp|avif|css|js|map|txt|xml|json|woff2?|ttf|eot|mp4|webm)$/i;\n\n/** Skip framework internals, API routes, and static assets. */\nexport function shouldTrackPath(pathname: string): boolean {\n if (pathname.startsWith('/_next/')) return false;\n if (pathname.startsWith('/api/')) return false;\n if (ASSET_EXT.test(pathname)) return false;\n return true;\n}\n"],"mappings":";AAEA,MAAM,YACJ;;AAGF,SAAgB,gBAAgB,UAA2B;CACzD,IAAI,SAAS,WAAW,SAAS,GAAG,OAAO;CAC3C,IAAI,SAAS,WAAW,OAAO,GAAG,OAAO;CACzC,IAAI,UAAU,KAAK,QAAQ,GAAG,OAAO;CACrC,OAAO;AACT"}
package/dist/next.cjs ADDED
@@ -0,0 +1,39 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_core = require("./core-KgLaVgR4.cjs");
3
+ const require_matchers = require("./matchers-CMwG37qc.cjs");
4
+ //#region src/next.ts
5
+ function extractRequestData(req) {
6
+ const ip = (req.headers.get("x-forwarded-for") ?? "").split(",")[0]?.trim() || void 0;
7
+ return {
8
+ url: req.nextUrl.href || req.url,
9
+ path: req.nextUrl.pathname,
10
+ referrer: req.headers.get("referer") ?? void 0,
11
+ userAgent: req.headers.get("user-agent") ?? void 0,
12
+ ip
13
+ };
14
+ }
15
+ /**
16
+ * Returns a fire-and-forget tracker. Call it from your Next middleware:
17
+ * const track = createAnalyticsMiddleware({ writeKey, domain: 'example.com' });
18
+ * export function middleware(req, event) { track(req, event); return NextResponse.next(); }
19
+ */
20
+ function createAnalyticsMiddleware(config) {
21
+ const client = require_core.createClient({
22
+ ...config,
23
+ keepalive: false
24
+ });
25
+ const matcher = config.shouldTrack ?? require_matchers.shouldTrackPath;
26
+ return (req, event) => {
27
+ try {
28
+ if (!matcher(req.nextUrl.pathname)) return;
29
+ const data = extractRequestData(req);
30
+ event.waitUntil(client.pageView(data));
31
+ } catch {}
32
+ };
33
+ }
34
+ //#endregion
35
+ exports.createAnalyticsMiddleware = createAnalyticsMiddleware;
36
+ exports.extractRequestData = extractRequestData;
37
+ exports.shouldTrackPath = require_matchers.shouldTrackPath;
38
+
39
+ //# sourceMappingURL=next.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.cjs","names":["createClient","shouldTrackPath"],"sources":["../src/next.ts"],"sourcesContent":["import { type AnalyticsClient, type ClientConfig, createClient } from './core';\nimport { shouldTrackPath } from './matchers';\n\nexport { shouldTrackPath } from './matchers';\n\n// Minimal structural types — avoids a hard runtime dependency on `next`.\ninterface NextRequestLike {\n url: string;\n nextUrl: { pathname: string; href: string };\n headers: Headers;\n}\ninterface FetchEventLike {\n waitUntil(promise: Promise<unknown>): void;\n}\n\nexport interface RequestData {\n url: string;\n path: string;\n referrer?: string;\n userAgent?: string;\n ip?: string;\n}\n\nexport function extractRequestData(req: NextRequestLike): RequestData {\n const xff = req.headers.get('x-forwarded-for') ?? '';\n const ip = xff.split(',')[0]?.trim() || undefined;\n return {\n url: req.nextUrl.href || req.url,\n path: req.nextUrl.pathname,\n referrer: req.headers.get('referer') ?? undefined,\n userAgent: req.headers.get('user-agent') ?? undefined,\n ip,\n };\n}\n\nexport interface MiddlewareConfig extends ClientConfig {\n /** Override the default path matcher. */\n shouldTrack?: (pathname: string) => boolean;\n}\n\n/**\n * Returns a fire-and-forget tracker. Call it from your Next middleware:\n * const track = createAnalyticsMiddleware({ writeKey, domain: 'example.com' });\n * export function middleware(req, event) { track(req, event); return NextResponse.next(); }\n */\nexport function createAnalyticsMiddleware(\n config: MiddlewareConfig,\n): (req: NextRequestLike, event: FetchEventLike) => void {\n // dispatch rides event.waitUntil — keepalive off, like the Cloudflare adapter\n const client: AnalyticsClient = createClient({ ...config, keepalive: false });\n const matcher = config.shouldTrack ?? shouldTrackPath;\n\n return (req, event) => {\n try {\n if (!matcher(req.nextUrl.pathname)) return;\n const data = extractRequestData(req);\n event.waitUntil(client.pageView(data));\n } catch {\n // never block the request\n }\n };\n}\n"],"mappings":";;;;AAuBA,SAAgB,mBAAmB,KAAmC;CAEpE,MAAM,MADM,IAAI,QAAQ,IAAI,iBAAiB,KAAK,GAAA,CACnC,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,KAAK,KAAA;CACxC,OAAO;EACL,KAAK,IAAI,QAAQ,QAAQ,IAAI;EAC7B,MAAM,IAAI,QAAQ;EAClB,UAAU,IAAI,QAAQ,IAAI,SAAS,KAAK,KAAA;EACxC,WAAW,IAAI,QAAQ,IAAI,YAAY,KAAK,KAAA;EAC5C;CACF;AACF;;;;;;AAYA,SAAgB,0BACd,QACuD;CAEvD,MAAM,SAA0BA,aAAAA,aAAa;EAAE,GAAG;EAAQ,WAAW;CAAM,CAAC;CAC5E,MAAM,UAAU,OAAO,eAAeC,iBAAAA;CAEtC,QAAQ,KAAK,UAAU;EACrB,IAAI;GACF,IAAI,CAAC,QAAQ,IAAI,QAAQ,QAAQ,GAAG;GACpC,MAAM,OAAO,mBAAmB,GAAG;GACnC,MAAM,UAAU,OAAO,SAAS,IAAI,CAAC;EACvC,QAAQ,CAER;CACF;AACF"}
@@ -0,0 +1,39 @@
1
+ import { n as ClientConfig } from "./core-DVilpn_i.cjs";
2
+
3
+ //#region src/matchers.d.ts
4
+ /** Skip framework internals, API routes, and static assets. */
5
+ declare function shouldTrackPath(pathname: string): boolean;
6
+ //#endregion
7
+ //#region src/next.d.ts
8
+ interface NextRequestLike {
9
+ url: string;
10
+ nextUrl: {
11
+ pathname: string;
12
+ href: string;
13
+ };
14
+ headers: Headers;
15
+ }
16
+ interface FetchEventLike {
17
+ waitUntil(promise: Promise<unknown>): void;
18
+ }
19
+ interface RequestData {
20
+ url: string;
21
+ path: string;
22
+ referrer?: string;
23
+ userAgent?: string;
24
+ ip?: string;
25
+ }
26
+ declare function extractRequestData(req: NextRequestLike): RequestData;
27
+ interface MiddlewareConfig extends ClientConfig {
28
+ /** Override the default path matcher. */
29
+ shouldTrack?: (pathname: string) => boolean;
30
+ }
31
+ /**
32
+ * Returns a fire-and-forget tracker. Call it from your Next middleware:
33
+ * const track = createAnalyticsMiddleware({ writeKey, domain: 'example.com' });
34
+ * export function middleware(req, event) { track(req, event); return NextResponse.next(); }
35
+ */
36
+ declare function createAnalyticsMiddleware(config: MiddlewareConfig): (req: NextRequestLike, event: FetchEventLike) => void;
37
+ //#endregion
38
+ export { MiddlewareConfig, RequestData, createAnalyticsMiddleware, extractRequestData, shouldTrackPath };
39
+ //# sourceMappingURL=next.d.cts.map
@@ -0,0 +1,39 @@
1
+ import { n as ClientConfig } from "./core-DVilpn_i.mjs";
2
+
3
+ //#region src/matchers.d.ts
4
+ /** Skip framework internals, API routes, and static assets. */
5
+ declare function shouldTrackPath(pathname: string): boolean;
6
+ //#endregion
7
+ //#region src/next.d.ts
8
+ interface NextRequestLike {
9
+ url: string;
10
+ nextUrl: {
11
+ pathname: string;
12
+ href: string;
13
+ };
14
+ headers: Headers;
15
+ }
16
+ interface FetchEventLike {
17
+ waitUntil(promise: Promise<unknown>): void;
18
+ }
19
+ interface RequestData {
20
+ url: string;
21
+ path: string;
22
+ referrer?: string;
23
+ userAgent?: string;
24
+ ip?: string;
25
+ }
26
+ declare function extractRequestData(req: NextRequestLike): RequestData;
27
+ interface MiddlewareConfig extends ClientConfig {
28
+ /** Override the default path matcher. */
29
+ shouldTrack?: (pathname: string) => boolean;
30
+ }
31
+ /**
32
+ * Returns a fire-and-forget tracker. Call it from your Next middleware:
33
+ * const track = createAnalyticsMiddleware({ writeKey, domain: 'example.com' });
34
+ * export function middleware(req, event) { track(req, event); return NextResponse.next(); }
35
+ */
36
+ declare function createAnalyticsMiddleware(config: MiddlewareConfig): (req: NextRequestLike, event: FetchEventLike) => void;
37
+ //#endregion
38
+ export { MiddlewareConfig, RequestData, createAnalyticsMiddleware, extractRequestData, shouldTrackPath };
39
+ //# sourceMappingURL=next.d.mts.map
package/dist/next.mjs ADDED
@@ -0,0 +1,36 @@
1
+ import { t as createClient } from "./core-Cvg8zn6Q.mjs";
2
+ import { t as shouldTrackPath } from "./matchers-CUD5NL5K.mjs";
3
+ //#region src/next.ts
4
+ function extractRequestData(req) {
5
+ const ip = (req.headers.get("x-forwarded-for") ?? "").split(",")[0]?.trim() || void 0;
6
+ return {
7
+ url: req.nextUrl.href || req.url,
8
+ path: req.nextUrl.pathname,
9
+ referrer: req.headers.get("referer") ?? void 0,
10
+ userAgent: req.headers.get("user-agent") ?? void 0,
11
+ ip
12
+ };
13
+ }
14
+ /**
15
+ * Returns a fire-and-forget tracker. Call it from your Next middleware:
16
+ * const track = createAnalyticsMiddleware({ writeKey, domain: 'example.com' });
17
+ * export function middleware(req, event) { track(req, event); return NextResponse.next(); }
18
+ */
19
+ function createAnalyticsMiddleware(config) {
20
+ const client = createClient({
21
+ ...config,
22
+ keepalive: false
23
+ });
24
+ const matcher = config.shouldTrack ?? shouldTrackPath;
25
+ return (req, event) => {
26
+ try {
27
+ if (!matcher(req.nextUrl.pathname)) return;
28
+ const data = extractRequestData(req);
29
+ event.waitUntil(client.pageView(data));
30
+ } catch {}
31
+ };
32
+ }
33
+ //#endregion
34
+ export { createAnalyticsMiddleware, extractRequestData, shouldTrackPath };
35
+
36
+ //# sourceMappingURL=next.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.mjs","names":[],"sources":["../src/next.ts"],"sourcesContent":["import { type AnalyticsClient, type ClientConfig, createClient } from './core';\nimport { shouldTrackPath } from './matchers';\n\nexport { shouldTrackPath } from './matchers';\n\n// Minimal structural types — avoids a hard runtime dependency on `next`.\ninterface NextRequestLike {\n url: string;\n nextUrl: { pathname: string; href: string };\n headers: Headers;\n}\ninterface FetchEventLike {\n waitUntil(promise: Promise<unknown>): void;\n}\n\nexport interface RequestData {\n url: string;\n path: string;\n referrer?: string;\n userAgent?: string;\n ip?: string;\n}\n\nexport function extractRequestData(req: NextRequestLike): RequestData {\n const xff = req.headers.get('x-forwarded-for') ?? '';\n const ip = xff.split(',')[0]?.trim() || undefined;\n return {\n url: req.nextUrl.href || req.url,\n path: req.nextUrl.pathname,\n referrer: req.headers.get('referer') ?? undefined,\n userAgent: req.headers.get('user-agent') ?? undefined,\n ip,\n };\n}\n\nexport interface MiddlewareConfig extends ClientConfig {\n /** Override the default path matcher. */\n shouldTrack?: (pathname: string) => boolean;\n}\n\n/**\n * Returns a fire-and-forget tracker. Call it from your Next middleware:\n * const track = createAnalyticsMiddleware({ writeKey, domain: 'example.com' });\n * export function middleware(req, event) { track(req, event); return NextResponse.next(); }\n */\nexport function createAnalyticsMiddleware(\n config: MiddlewareConfig,\n): (req: NextRequestLike, event: FetchEventLike) => void {\n // dispatch rides event.waitUntil — keepalive off, like the Cloudflare adapter\n const client: AnalyticsClient = createClient({ ...config, keepalive: false });\n const matcher = config.shouldTrack ?? shouldTrackPath;\n\n return (req, event) => {\n try {\n if (!matcher(req.nextUrl.pathname)) return;\n const data = extractRequestData(req);\n event.waitUntil(client.pageView(data));\n } catch {\n // never block the request\n }\n };\n}\n"],"mappings":";;;AAuBA,SAAgB,mBAAmB,KAAmC;CAEpE,MAAM,MADM,IAAI,QAAQ,IAAI,iBAAiB,KAAK,GAAA,CACnC,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,KAAK,KAAA;CACxC,OAAO;EACL,KAAK,IAAI,QAAQ,QAAQ,IAAI;EAC7B,MAAM,IAAI,QAAQ;EAClB,UAAU,IAAI,QAAQ,IAAI,SAAS,KAAK,KAAA;EACxC,WAAW,IAAI,QAAQ,IAAI,YAAY,KAAK,KAAA;EAC5C;CACF;AACF;;;;;;AAYA,SAAgB,0BACd,QACuD;CAEvD,MAAM,SAA0B,aAAa;EAAE,GAAG;EAAQ,WAAW;CAAM,CAAC;CAC5E,MAAM,UAAU,OAAO,eAAe;CAEtC,QAAQ,KAAK,UAAU;EACrB,IAAI;GACF,IAAI,CAAC,QAAQ,IAAI,QAAQ,QAAQ,GAAG;GACpC,MAAM,OAAO,mBAAmB,GAAG;GACnC,MAAM,UAAU,OAAO,SAAS,IAAI,CAAC;EACvC,QAAQ,CAER;CACF;AACF"}