@analytickit/sdk-node 0.0.0-stage → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AnalyticKit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,108 @@
1
- # Temporary Holding Version
1
+ # `@analytickit/sdk-node`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **Status: published.** `npm install @analytickit/sdk-node`
4
+
5
+ Node client for AgentGraph. Two things live in this package:
6
+
7
+ 1. A plain `track()`/`trackBatch()` client — for an AI agent, job, or backend
8
+ service that wants to emit its own events.
9
+ 2. Request middleware (`trackRequest()` plus three framework adapters) that
10
+ turns any Node server into a server-side event collector, so traffic that
11
+ never runs the browser SDK's JavaScript — AI crawlers in particular —
12
+ still produces events.
13
+
14
+ Full install guide and the F0.3 access-log import mapping:
15
+ [`docs/help/server-side-collection.md`](../../docs/help/server-side-collection.md).
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install @analytickit/sdk-node
21
+ ```
22
+
23
+ (Inside this monorepo, import it as `@analytickit/sdk-node` from any
24
+ workspace, no install step needed.)
25
+
26
+ ## Client
27
+
28
+ ```ts
29
+ import { AnalyticKit } from '@analytickit/sdk-node';
30
+
31
+ const client = new AnalyticKit({
32
+ apiKey: process.env.ANALYTICKIT_SDK_KEY!,
33
+ endpoint: 'https://app.analytickit.com', // bare origin — no trailing /v1
34
+ // actorType: 'human' (default) | 'ai_agent' | 'cli' | ...
35
+ // maxRetries: 2 (default) — only retries 5xx and network errors, never 4xx
36
+ // timeout: 5000 (default, ms)
37
+ });
38
+ ```
39
+
40
+ ### `track(eventName, options?)`
41
+
42
+ ```ts
43
+ await client.track('report_generated', {
44
+ actorType: 'ai_agent',
45
+ agentId: 'agent_123',
46
+ taskId: 'task_456',
47
+ properties: { reportType: 'weekly_summary' },
48
+ });
49
+ ```
50
+
51
+ ### `trackBatch(events)`
52
+
53
+ ```ts
54
+ const { accepted, rejected } = await client.trackBatch([
55
+ { eventName: 'agent_step', taskId: 't1' },
56
+ { eventName: 'agent_step', taskId: 't1' },
57
+ ]);
58
+ ```
59
+
60
+ ### `trackRequest(context, options?)`
61
+
62
+ Records one inbound HTTP request as observed by a server-side collector.
63
+ Normally you don't call this directly — the middleware adapters below call
64
+ it for you — but it's the primitive they're built on, useful for a custom
65
+ collector (e.g. one that wants to attach a response `status`):
66
+
67
+ ```ts
68
+ await client.trackRequest(
69
+ { userAgent, ip, path, referrer, host, method, status },
70
+ { eventName: 'page_view', visitorId, sessionId },
71
+ );
72
+ ```
73
+
74
+ `request_context` on the sent payload takes priority over ingest's own
75
+ transport headers for Web2 enrichment — see the install guide, §3.
76
+
77
+ ## Middleware adapters
78
+
79
+ All three are re-exported from the package root (as well as from
80
+ `@analytickit/sdk-node/middleware`):
81
+
82
+ ```ts
83
+ import {
84
+ analyticKitExpress,
85
+ analyticKitFastify,
86
+ analyticKitFetch,
87
+ extractContext,
88
+ shouldCollect,
89
+ DEFAULT_IGNORE,
90
+ } from '@analytickit/sdk-node';
91
+ ```
92
+
93
+ | Export | For |
94
+ | --- | --- |
95
+ | `analyticKitExpress(options)` | `app.use(...)` |
96
+ | `analyticKitFastify(options)` | `app.addHook('onRequest', ...)` |
97
+ | `analyticKitFetch(options)` | Next.js middleware, Hono, edge runtimes — anything handed a Fetch API `Request` |
98
+ | `extractContext(req, options?)` | Pull a `RequestContext` off a node-style request object directly |
99
+ | `shouldCollect(path, options)` | The ignore/sample-rate decision, exposed standalone |
100
+ | `DEFAULT_IGNORE` | The default `ignore` regex list (static assets, health checks, `_next`, favicon) |
101
+
102
+ All three take `MiddlewareOptions` — at minimum `{ client }`. See the
103
+ install guide for `ignore`, `sampleRate`, `trustProxy`, and `onError`, and
104
+ for why collection adds no request latency and cannot fail the request.
105
+
106
+ Full docs, the privacy/`capture_ip` behavior, volume-control gotchas, and
107
+ the F0.3 CSV access-log import mapping:
108
+ [`docs/help/server-side-collection.md`](../../docs/help/server-side-collection.md).
@@ -0,0 +1,31 @@
1
+ import type { NodeSdkConfig, TrackOptions, RequestContext, TrackRequestOptions } from './types.js';
2
+ export type { NodeSdkConfig, TrackOptions, RequestContext, TrackRequestOptions } from './types.js';
3
+ export { analyticKitExpress, analyticKitFastify, analyticKitFetch, extractContext, shouldCollect, DEFAULT_IGNORE, detectAgentProtocol, } from './middleware.js';
4
+ export type { MiddlewareOptions, NodeLikeRequest } from './middleware.js';
5
+ export declare class AnalyticKit {
6
+ private readonly apiKey;
7
+ private readonly endpoint;
8
+ private readonly actorType;
9
+ private readonly maxRetries;
10
+ private readonly timeout;
11
+ constructor(config: NodeSdkConfig);
12
+ track(eventName: string, options?: Partial<Omit<TrackOptions, 'eventName'>>): Promise<void>;
13
+ /**
14
+ * Record a single inbound HTTP request observed by a server-side collector.
15
+ *
16
+ * The ingest side reads `request_context` in preference to the transport
17
+ * headers on our own call, because those describe THIS process, not the end
18
+ * user. `page_path` is mirrored to the top level so the event also lights up
19
+ * the existing funnel UI without a server round-trip.
20
+ */
21
+ trackRequest(context: RequestContext, options?: TrackRequestOptions): Promise<void>;
22
+ trackBatch(events: TrackOptions[]): Promise<{
23
+ accepted: number;
24
+ rejected: number;
25
+ }>;
26
+ shutdown(): Promise<void>;
27
+ private _sendWithRetry;
28
+ private _sleep;
29
+ }
30
+ export default AnalyticKit;
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGnG,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACnG,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,cAAc,EACd,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE1E,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,MAAM,EAAE,aAAa;IAW3B,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BjG;;;;;;;OAOG;IACG,YAAY,CAChB,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,IAAI,CAAC;IAyCV,UAAU,CACd,MAAM,EAAE,YAAY,EAAE,GACrB,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IA+B5C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAIjB,cAAc;IA0C5B,OAAO,CAAC,MAAM;CAGf;AAED,eAAe,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AnalyticKit = exports.detectAgentProtocol = exports.DEFAULT_IGNORE = exports.shouldCollect = exports.extractContext = exports.analyticKitFetch = exports.analyticKitFastify = exports.analyticKitExpress = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const types_js_1 = require("./types.js");
6
+ var middleware_js_1 = require("./middleware.js");
7
+ Object.defineProperty(exports, "analyticKitExpress", { enumerable: true, get: function () { return middleware_js_1.analyticKitExpress; } });
8
+ Object.defineProperty(exports, "analyticKitFastify", { enumerable: true, get: function () { return middleware_js_1.analyticKitFastify; } });
9
+ Object.defineProperty(exports, "analyticKitFetch", { enumerable: true, get: function () { return middleware_js_1.analyticKitFetch; } });
10
+ Object.defineProperty(exports, "extractContext", { enumerable: true, get: function () { return middleware_js_1.extractContext; } });
11
+ Object.defineProperty(exports, "shouldCollect", { enumerable: true, get: function () { return middleware_js_1.shouldCollect; } });
12
+ Object.defineProperty(exports, "DEFAULT_IGNORE", { enumerable: true, get: function () { return middleware_js_1.DEFAULT_IGNORE; } });
13
+ Object.defineProperty(exports, "detectAgentProtocol", { enumerable: true, get: function () { return middleware_js_1.detectAgentProtocol; } });
14
+ class AnalyticKit {
15
+ apiKey;
16
+ endpoint;
17
+ actorType;
18
+ maxRetries;
19
+ timeout;
20
+ constructor(config) {
21
+ if (!config.apiKey) {
22
+ throw new Error('AnalyticKit: apiKey is required');
23
+ }
24
+ this.apiKey = config.apiKey;
25
+ this.endpoint = config.endpoint.replace(/\/$/, '');
26
+ this.actorType = config.actorType ?? types_js_1.NODE_CONFIG_DEFAULTS.actorType;
27
+ this.maxRetries = config.maxRetries ?? types_js_1.NODE_CONFIG_DEFAULTS.maxRetries;
28
+ this.timeout = config.timeout ?? types_js_1.NODE_CONFIG_DEFAULTS.timeout;
29
+ }
30
+ async track(eventName, options) {
31
+ // Minted here, BEFORE _sendWithRetry — the whole point is that all
32
+ // retries of this event carry the same key, so ingest derives one
33
+ // event_id and the duplicate is absorbed. _sendWithRetry re-sends the
34
+ // identical body string, so the key is stable by construction
35
+ // (2026-07-04 critique, Arch #6: a 5xx retry used to create a second row).
36
+ const payload = {
37
+ idempotency_key: options?.idempotencyKey ?? (0, node_crypto_1.randomUUID)(),
38
+ event_name: eventName,
39
+ timestamp: Date.now(),
40
+ actor_type: options?.actorType ?? this.actorType,
41
+ properties: options?.properties,
42
+ agent_id: options?.agentId,
43
+ task_id: options?.taskId,
44
+ workflow_id: options?.workflowId,
45
+ wallet_address: options?.walletAddress,
46
+ chain: options?.chain,
47
+ };
48
+ await this._sendWithRetry(`${this.endpoint}/v1/events`, JSON.stringify(payload), this.maxRetries);
49
+ }
50
+ /**
51
+ * Record a single inbound HTTP request observed by a server-side collector.
52
+ *
53
+ * The ingest side reads `request_context` in preference to the transport
54
+ * headers on our own call, because those describe THIS process, not the end
55
+ * user. `page_path` is mirrored to the top level so the event also lights up
56
+ * the existing funnel UI without a server round-trip.
57
+ */
58
+ async trackRequest(context, options) {
59
+ const requestContext = {};
60
+ const map = [
61
+ ['userAgent', 'user_agent'],
62
+ ['ip', 'ip'],
63
+ ['path', 'path'],
64
+ ['referrer', 'referrer'],
65
+ ['host', 'host'],
66
+ ['method', 'method'],
67
+ ['status', 'status'],
68
+ ['protocol', 'protocol'],
69
+ ];
70
+ for (const [from, to] of map) {
71
+ const value = context[from];
72
+ // Omit absent fields entirely — sending explicit nulls would make the
73
+ // ingest-side "was this forwarded?" check ambiguous.
74
+ if (value !== undefined && value !== null && value !== '') {
75
+ requestContext[to] = value;
76
+ }
77
+ }
78
+ const payload = {
79
+ idempotency_key: options?.idempotencyKey ?? (0, node_crypto_1.randomUUID)(),
80
+ event_name: options?.eventName ?? 'page_view',
81
+ timestamp: Date.now(),
82
+ actor_type: options?.actorType ?? this.actorType,
83
+ properties: options?.properties,
84
+ visitor_id: options?.visitorId,
85
+ session_id: options?.sessionId,
86
+ page_path: context.path ?? undefined,
87
+ page_referrer: context.referrer ?? undefined,
88
+ request_context: requestContext,
89
+ };
90
+ await this._sendWithRetry(`${this.endpoint}/v1/events`, JSON.stringify(payload), this.maxRetries);
91
+ }
92
+ async trackBatch(events) {
93
+ const payloads = events.map((e) => ({
94
+ idempotency_key: e.idempotencyKey ?? (0, node_crypto_1.randomUUID)(),
95
+ event_name: e.eventName,
96
+ timestamp: Date.now(),
97
+ actor_type: e.actorType ?? this.actorType,
98
+ properties: e.properties,
99
+ agent_id: e.agentId,
100
+ task_id: e.taskId,
101
+ workflow_id: e.workflowId,
102
+ wallet_address: e.walletAddress,
103
+ chain: e.chain,
104
+ }));
105
+ const res = await this._sendWithRetry(`${this.endpoint}/v1/events/batch`, JSON.stringify(payloads), this.maxRetries);
106
+ if (res) {
107
+ const body = (await res.json());
108
+ return {
109
+ accepted: body.data?.accepted ?? events.length,
110
+ rejected: body.data?.rejected ?? 0,
111
+ };
112
+ }
113
+ return { accepted: events.length, rejected: 0 };
114
+ }
115
+ async shutdown() {
116
+ // No-op for now; future queue support would flush pending here
117
+ }
118
+ async _sendWithRetry(url, body, retriesLeft) {
119
+ const attempt = this.maxRetries - retriesLeft;
120
+ try {
121
+ const controller = new AbortController();
122
+ const timer = setTimeout(() => controller.abort(), this.timeout);
123
+ const res = await fetch(url, {
124
+ method: 'POST',
125
+ headers: {
126
+ 'Content-Type': 'application/json',
127
+ 'Authorization': `Bearer ${this.apiKey}`,
128
+ },
129
+ body,
130
+ signal: controller.signal,
131
+ });
132
+ clearTimeout(timer);
133
+ if (res.status >= 500 && retriesLeft > 0) {
134
+ const backoff = 100 * Math.pow(2, attempt);
135
+ await this._sleep(backoff);
136
+ return this._sendWithRetry(url, body, retriesLeft - 1);
137
+ }
138
+ // 4xx: do NOT retry — return as-is
139
+ return res;
140
+ }
141
+ catch (err) {
142
+ // Network errors: retry if possible
143
+ if (retriesLeft > 0) {
144
+ const backoff = 100 * Math.pow(2, attempt);
145
+ await this._sleep(backoff);
146
+ return this._sendWithRetry(url, body, retriesLeft - 1);
147
+ }
148
+ return null;
149
+ }
150
+ }
151
+ _sleep(ms) {
152
+ return new Promise((resolve) => setTimeout(resolve, ms));
153
+ }
154
+ }
155
+ exports.AnalyticKit = AnalyticKit;
156
+ exports.default = AnalyticKit;
157
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import type { AnalyticKit } from './index.js';
2
+ import type { RequestContext, TrackRequestOptions } from './types.js';
3
+ /**
4
+ * Structural stand-ins for `http.IncomingMessage` and the Express/Fastify
5
+ * request objects that wrap it. Typed here rather than imported so this
6
+ * package keeps its zero-runtime-dependency guarantee.
7
+ */
8
+ export interface NodeLikeRequest {
9
+ method?: string;
10
+ url?: string;
11
+ originalUrl?: string;
12
+ headers: Record<string, string | string[] | undefined>;
13
+ socket?: {
14
+ remoteAddress?: string;
15
+ };
16
+ }
17
+ export interface MiddlewareOptions {
18
+ client: AnalyticKit;
19
+ /** Event name for collected requests. Default 'page_view'. */
20
+ eventName?: string;
21
+ /**
22
+ * Paths matching any of these are not collected. REPLACES the defaults —
23
+ * pass `[...DEFAULT_IGNORE, /^\/admin/]` to extend them instead.
24
+ */
25
+ ignore?: RegExp[];
26
+ /** 0..1. Default 1 (collect everything). */
27
+ sampleRate?: number;
28
+ /** Read the client IP from X-Forwarded-For. Default true. */
29
+ trustProxy?: boolean;
30
+ /** Called instead of throwing when collection faults. Default: swallow. */
31
+ onError?: (err: unknown) => void;
32
+ /** Extra options forwarded to trackRequest (actorType, properties, ...). */
33
+ track?: Omit<TrackRequestOptions, 'eventName'>;
34
+ }
35
+ /**
36
+ * Requests a page-analytics product should not count. Static assets dominate
37
+ * request volume on any real site and would swamp both the customer's event
38
+ * quota and the AI-traffic signal this feature exists to surface.
39
+ */
40
+ export declare const DEFAULT_IGNORE: RegExp[];
41
+ /**
42
+ * Infer the agent protocol from request headers. `get` is a case-insensitive
43
+ * header lookup (`headerValue` for node-style requests, `Headers.get` for
44
+ * Fetch). Only the derived label is ever returned — header VALUES (session
45
+ * ids, payment payloads) never pass through this function's result.
46
+ *
47
+ * x402 wins when both signals are present: a paid MCP call is still a
48
+ * payment event.
49
+ */
50
+ export declare function detectAgentProtocol(get: (name: string) => string | null | undefined): 'mcp' | 'x402' | null;
51
+ /** True when this path should produce an event. */
52
+ export declare function shouldCollect(path: string, options: Pick<MiddlewareOptions, 'ignore' | 'sampleRate'>): boolean;
53
+ /** Pull the END USER's signals off a node-style request object. */
54
+ export declare function extractContext(req: NodeLikeRequest, options?: Pick<MiddlewareOptions, 'trustProxy'>): RequestContext;
55
+ /**
56
+ * Express / Connect middleware.
57
+ *
58
+ * app.use(analyticKitExpress({ client }));
59
+ *
60
+ * `next()` is called synchronously before any I/O, so the collector adds no
61
+ * latency and cannot fail the request.
62
+ */
63
+ export declare function analyticKitExpress(options: MiddlewareOptions): (req: NodeLikeRequest, res: unknown, next: () => void) => void;
64
+ /**
65
+ * Fastify onRequest hook.
66
+ *
67
+ * app.addHook('onRequest', analyticKitFastify({ client }));
68
+ */
69
+ export declare function analyticKitFastify(options: MiddlewareOptions): (req: NodeLikeRequest, reply: unknown, done: () => void) => void;
70
+ /**
71
+ * Fetch-API collector for Next.js middleware, Hono, and edge runtimes.
72
+ *
73
+ * export function middleware(request: NextRequest) {
74
+ * collect(request);
75
+ * return NextResponse.next();
76
+ * }
77
+ */
78
+ export declare function analyticKitFetch(options: MiddlewareOptions): (request: Request) => void;
79
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,MAAM,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,4EAA4E;IAC5E,KAAK,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAC;CAChD;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,EAalC,CAAC;AAsBF;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/C,KAAK,GAAG,MAAM,GAAG,IAAI,CAIvB;AAED,mDAAmD;AACnD,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,GAAG,YAAY,CAAC,GACxD,OAAO,CAYT;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAC5B,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAM,GAClD,cAAc,CAuBhB;AA6CD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,iBAAiB,GACzB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,CAchE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,iBAAiB,GACzB,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,CAWlE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,iBAAiB,GACzB,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CA4B5B"}
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_IGNORE = void 0;
4
+ exports.detectAgentProtocol = detectAgentProtocol;
5
+ exports.shouldCollect = shouldCollect;
6
+ exports.extractContext = extractContext;
7
+ exports.analyticKitExpress = analyticKitExpress;
8
+ exports.analyticKitFastify = analyticKitFastify;
9
+ exports.analyticKitFetch = analyticKitFetch;
10
+ /**
11
+ * Requests a page-analytics product should not count. Static assets dominate
12
+ * request volume on any real site and would swamp both the customer's event
13
+ * quota and the AI-traffic signal this feature exists to surface.
14
+ */
15
+ exports.DEFAULT_IGNORE = [
16
+ // Static assets. Anchored with `$` and matched against the PATHNAME only
17
+ // (see shouldCollect) — an unanchored form applied to path+query would drop
18
+ // a real page like `/search?q=file.js`.
19
+ /\.(?:js|mjs|cjs|css|map|png|jpe?g|gif|svg|webp|avif|ico|woff2?|ttf|eot|txt|xml|json)$/i,
20
+ // Health checks. These MUST be anchored: a bare `/^\/health/` also swallows
21
+ // real content pages such as `/health-insurance-quotes` or `/healthcare`,
22
+ // and silently losing a customer's pageviews is close to impossible to
23
+ // notice from the outside.
24
+ /^\/healthz?(?:\/|$)/,
25
+ /^\/_next\//,
26
+ /^\/__nextjs/,
27
+ /^\/favicon\.ico$/,
28
+ ];
29
+ function headerValue(headers, name) {
30
+ // Fast path: Node's own http parser (and Express/Fastify on top of it)
31
+ // already lowercases header names, which covers nearly every real request.
32
+ // Falls back to a case-insensitive scan for the rare proxy/framework that
33
+ // doesn't normalize, and for callers constructing headers by hand.
34
+ const direct = headers[name];
35
+ if (direct !== undefined)
36
+ return Array.isArray(direct) ? direct[0] : direct;
37
+ const lower = name.toLowerCase();
38
+ for (const key of Object.keys(headers)) {
39
+ if (key.toLowerCase() === lower) {
40
+ const raw = headers[key];
41
+ return Array.isArray(raw) ? raw[0] : raw;
42
+ }
43
+ }
44
+ return undefined;
45
+ }
46
+ /**
47
+ * Infer the agent protocol from request headers. `get` is a case-insensitive
48
+ * header lookup (`headerValue` for node-style requests, `Headers.get` for
49
+ * Fetch). Only the derived label is ever returned — header VALUES (session
50
+ * ids, payment payloads) never pass through this function's result.
51
+ *
52
+ * x402 wins when both signals are present: a paid MCP call is still a
53
+ * payment event.
54
+ */
55
+ function detectAgentProtocol(get) {
56
+ if (get('payment-signature') || get('x-payment'))
57
+ return 'x402';
58
+ if (get('mcp-session-id') || get('mcp-protocol-version'))
59
+ return 'mcp';
60
+ return null;
61
+ }
62
+ /** True when this path should produce an event. */
63
+ function shouldCollect(path, options) {
64
+ const ignore = options.ignore ?? exports.DEFAULT_IGNORE;
65
+ // Ignore patterns are matched against the pathname; the query string takes
66
+ // no part in the decision. Without this, `/search?q=file.js` reads as a
67
+ // JavaScript asset and the pageview is thrown away.
68
+ const pathname = path.split('?')[0] ?? path;
69
+ if (ignore.some((re) => re.test(pathname)))
70
+ return false;
71
+ const rate = options.sampleRate ?? 1;
72
+ if (rate >= 1)
73
+ return true;
74
+ if (rate <= 0)
75
+ return false;
76
+ return Math.random() < rate;
77
+ }
78
+ /** Pull the END USER's signals off a node-style request object. */
79
+ function extractContext(req, options = {}) {
80
+ const headers = req.headers ?? {};
81
+ const trustProxy = options.trustProxy ?? true;
82
+ let ip = req.socket?.remoteAddress ?? null;
83
+ if (trustProxy) {
84
+ const xff = headerValue(headers, 'x-forwarded-for');
85
+ // "client, proxy1, proxy2" — the first hop is the client.
86
+ const first = xff?.split(',')[0]?.trim();
87
+ if (first)
88
+ ip = first;
89
+ }
90
+ return {
91
+ userAgent: headerValue(headers, 'user-agent') ?? null,
92
+ ip,
93
+ path: req.originalUrl ?? req.url ?? null,
94
+ // Note the historical misspelling: the HTTP header is `Referer`.
95
+ referrer: headerValue(headers, 'referer') ?? headerValue(headers, 'referrer') ?? null,
96
+ host: headerValue(headers, 'host') ?? null,
97
+ method: req.method ?? null,
98
+ status: null,
99
+ protocol: detectAgentProtocol((name) => headerValue(headers, name)),
100
+ };
101
+ }
102
+ /**
103
+ * Report a collection fault to the caller's handler, absorbing anything that
104
+ * handler itself throws.
105
+ *
106
+ * The customer's `onError` is arbitrary code we invoke from inside a promise
107
+ * rejection handler. If it throws there, the rejection is unhandled — and Node
108
+ * 15+ terminates the process on an unhandled rejection by default. That would
109
+ * turn "your analytics had a hiccup" into "your server died", which is the
110
+ * exact outcome this middleware exists to make impossible. There is nowhere
111
+ * left to report a failure of the failure reporter, so it is swallowed.
112
+ */
113
+ function reportError(options, err) {
114
+ try {
115
+ options.onError?.(err);
116
+ }
117
+ catch {
118
+ // Deliberately empty — see above.
119
+ }
120
+ }
121
+ /**
122
+ * Build the context and hand it to the client, fire-and-forget.
123
+ *
124
+ * Takes a factory rather than a value so that a throw from `extractContext`
125
+ * lands inside this try block. Passing `extractContext(req)` as an argument
126
+ * would evaluate it BEFORE the try, leaving that throw to escape into the
127
+ * host app after next()/done() had already run.
128
+ */
129
+ function dispatch(buildContext, options) {
130
+ try {
131
+ const result = options.client.trackRequest(buildContext(), {
132
+ ...options.track,
133
+ eventName: options.eventName ?? 'page_view',
134
+ });
135
+ // Fire-and-forget: an ingest outage must not surface in the host app.
136
+ void Promise.resolve(result).catch((err) => reportError(options, err));
137
+ }
138
+ catch (err) {
139
+ reportError(options, err);
140
+ }
141
+ }
142
+ /**
143
+ * Express / Connect middleware.
144
+ *
145
+ * app.use(analyticKitExpress({ client }));
146
+ *
147
+ * `next()` is called synchronously before any I/O, so the collector adds no
148
+ * latency and cannot fail the request.
149
+ */
150
+ function analyticKitExpress(options) {
151
+ return (req, _res, next) => {
152
+ next();
153
+ // Everything after next() is wrapped: the contract this middleware sells
154
+ // is that installing it cannot break the host app, and that has to hold
155
+ // for a malformed request object or a pathological ignore pattern too.
156
+ try {
157
+ const path = req.originalUrl ?? req.url ?? '/';
158
+ if (!shouldCollect(path, options))
159
+ return;
160
+ dispatch(() => extractContext(req, options), options);
161
+ }
162
+ catch (err) {
163
+ reportError(options, err);
164
+ }
165
+ };
166
+ }
167
+ /**
168
+ * Fastify onRequest hook.
169
+ *
170
+ * app.addHook('onRequest', analyticKitFastify({ client }));
171
+ */
172
+ function analyticKitFastify(options) {
173
+ return (req, _reply, done) => {
174
+ done();
175
+ try {
176
+ const path = req.originalUrl ?? req.url ?? '/';
177
+ if (!shouldCollect(path, options))
178
+ return;
179
+ dispatch(() => extractContext(req, options), options);
180
+ }
181
+ catch (err) {
182
+ reportError(options, err);
183
+ }
184
+ };
185
+ }
186
+ /**
187
+ * Fetch-API collector for Next.js middleware, Hono, and edge runtimes.
188
+ *
189
+ * export function middleware(request: NextRequest) {
190
+ * collect(request);
191
+ * return NextResponse.next();
192
+ * }
193
+ */
194
+ function analyticKitFetch(options) {
195
+ return (request) => {
196
+ // Same containment guarantee as the node adapters: a malformed URL or a
197
+ // hostile header must not propagate into the caller's request handler.
198
+ try {
199
+ const url = new URL(request.url);
200
+ const path = url.pathname + url.search;
201
+ if (!shouldCollect(path, options))
202
+ return;
203
+ const trustProxy = options.trustProxy ?? true;
204
+ dispatch(() => {
205
+ const xff = request.headers.get('x-forwarded-for');
206
+ return {
207
+ userAgent: request.headers.get('user-agent'),
208
+ ip: trustProxy ? (xff?.split(',')[0]?.trim() ?? null) : null,
209
+ path,
210
+ referrer: request.headers.get('referer') ?? request.headers.get('referrer'),
211
+ host: request.headers.get('host') ?? url.host,
212
+ method: request.method,
213
+ status: null,
214
+ protocol: detectAgentProtocol((name) => request.headers.get(name)),
215
+ };
216
+ }, options);
217
+ }
218
+ catch (err) {
219
+ reportError(options, err);
220
+ }
221
+ };
222
+ }
223
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ export interface NodeSdkConfig {
2
+ apiKey: string;
3
+ endpoint: string;
4
+ actorType?: string;
5
+ maxRetries?: number;
6
+ timeout?: number;
7
+ }
8
+ export declare const NODE_CONFIG_DEFAULTS: {
9
+ readonly actorType: "human";
10
+ readonly maxRetries: 2;
11
+ readonly timeout: 5000;
12
+ };
13
+ export interface TrackOptions {
14
+ eventName: string;
15
+ /**
16
+ * Optional caller-supplied idempotency key. Defaults to a fresh UUID per
17
+ * event, which makes the SDK's own 5xx retries exactly-once. Supply your
18
+ * own (e.g. an order id) when the CALLER may also retry — that is the only
19
+ * way the server can tell a re-send from a genuinely new event.
20
+ */
21
+ idempotencyKey?: string;
22
+ properties?: Record<string, unknown>;
23
+ actorType?: string;
24
+ agentId?: string;
25
+ taskId?: string;
26
+ workflowId?: string;
27
+ walletAddress?: string;
28
+ chain?: string;
29
+ }
30
+ /**
31
+ * The original request's signals, as observed by a server-side collector.
32
+ * Every field is optional: a collector behind a proxy that strips headers,
33
+ * or a client with `capture_ip` off, still produces useful rows.
34
+ */
35
+ export interface RequestContext {
36
+ userAgent?: string | null;
37
+ ip?: string | null;
38
+ path?: string | null;
39
+ referrer?: string | null;
40
+ host?: string | null;
41
+ method?: string | null;
42
+ status?: number | null;
43
+ /**
44
+ * Agent protocol inferred from request headers (`mcp` | `x402` | null).
45
+ * See `detectAgentProtocol` in `middleware.ts`. Header VALUES (session ids,
46
+ * payment payloads) are never captured — only this derived label.
47
+ */
48
+ protocol?: string | null;
49
+ }
50
+ export interface TrackRequestOptions {
51
+ /** Defaults to 'page_view' so server-collected traffic lands in the same
52
+ * Web2 enrichment path as the browser SDK. */
53
+ eventName?: string;
54
+ properties?: Record<string, unknown>;
55
+ actorType?: string;
56
+ idempotencyKey?: string;
57
+ visitorId?: string;
58
+ sessionId?: string;
59
+ }
60
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,oBAAoB;;;;CAIvB,CAAC;AAEX,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC;mDAC+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NODE_CONFIG_DEFAULTS = void 0;
4
+ exports.NODE_CONFIG_DEFAULTS = {
5
+ actorType: 'human',
6
+ maxRetries: 2,
7
+ timeout: 5000,
8
+ };
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AAQa,QAAA,oBAAoB,GAAG;IAClC,SAAS,EAAE,OAAO;IAClB,UAAU,EAAE,CAAC;IACb,OAAO,EAAE,IAAI;CACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,39 @@
1
1
  {
2
2
  "name": "@analytickit/sdk-node",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Server-side event tracking + request middleware for the AgentGraph / AnalyticKit API. Captures AI crawlers and agents (GPTBot, ClaudeBot, MCP, x402, ...) that never run browser JS.",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./middleware": {
14
+ "types": "./dist/middleware.d.ts",
15
+ "default": "./dist/middleware.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "prepublishOnly": "npm run build",
25
+ "test": "npx tsx --test test/*.test.ts"
26
+ },
27
+ "engines": {
28
+ "node": ">=20"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^20.0.0",
35
+ "tsx": "^4.0.0",
36
+ "typescript": "^5.4.0"
37
+ },
38
+ "homepage": "https://analytickit.com"
39
+ }