@doow/track 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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@doow/track` will be documented here.
4
+
5
+ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
+ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ ## [0.1.0] — 2026-04-20
11
+
12
+ Initial release.
13
+
14
+ ### Added
15
+
16
+ - `DoowTracker` — core tracking class with full init options surface (18 options)
17
+ - Automatic flush on `flushAt` (event count), `flushInterval` (timer), and `maxPayloadBytes` (size)
18
+ - Ring buffer with configurable `maxQueueSize` (drop oldest when full)
19
+ - Gzip compression with `Content-Encoding: gzip` (disable via `disableCompression`)
20
+ - Exponential backoff with ±20% jitter, configurable `retryCount`
21
+ - 429 rate limiting: respects `Retry-After` header + `X-Doow-Rate-Limits` per-category header
22
+ - 413 adaptive batch halving on payload-too-large
23
+ - 207 partial accept: surfaces rejected event IDs via `onError`
24
+ - 401 auth failure: stops SDK permanently, surfaces `AUTH_FAILURE` error
25
+ - `beforeSend` / `beforeFlush` hooks for per-event and per-batch filtering
26
+ - `FileOfflineStore` — atomic FIFO persistent store for failed batches; drains on reconnect
27
+ - Serverless wrappers: `withLambda`, `withVercel`, `withAzureFunction` — guaranteed flush before return
28
+ - Sidecar process: stdin / file-tail / TCP input modes, HTTP `/healthz` endpoint
29
+ - CLI daemon: `--config`, `--api-key`, `--pidfile`, `--version` flags, `SIGHUP` config reload
30
+ - Environment variable overrides for all major options (`DOOW_TRACK_*`)
31
+ - `__SDK_VERSION__` compile-time constant — wire protocol `sdk_version` and `X-Doow-SDK-Version` header track package.json version
32
+ - Full TypeScript types exported (`DoowTrackerOptions`, `TrackEvent`, `SdkError`, etc.)
33
+ - 137 unit tests across all stories
package/README.md ADDED
@@ -0,0 +1,222 @@
1
+ # @doow/track
2
+
3
+ Customer-facing SDK for emitting usage telemetry to Doow. Tracks metered usage events (API calls, tokens, storage, requests, etc.) from any Node.js application.
4
+
5
+ Source repository: https://github.com/Doow-Dev/doow-track-sdk
6
+
7
+ ## Quick start
8
+
9
+ ```ts
10
+ import { DoowTracker } from '@doow/track';
11
+
12
+ const meter = new DoowTracker('dk_your_api_key');
13
+ meter.track({ metric: 'api_calls', quantity: 1, license_id: 'lic_...' });
14
+ await meter.shutdown();
15
+ ```
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @doow/track
21
+ # or
22
+ yarn add @doow/track
23
+ ```
24
+
25
+ ## All init options
26
+
27
+ | Option | Type | Default | Description |
28
+ |--------|------|---------|-------------|
29
+ | `endpoint` | `string` | `https://api.doow.co` | Telemetry server endpoint |
30
+ | `enabled` | `boolean` | `true` | Enable/disable the SDK. When `false`, all operations are no-ops |
31
+ | `attribution` | `Record<string, string \| number \| boolean>` | `undefined` | SDK-level attribution bag merged into every event |
32
+ | `debug` | `boolean` | `false` | Enable debug logging (only works in non-production builds) |
33
+ | `flushAt` | `number` | `20` | Flush after N events queued |
34
+ | `flushInterval` | `number` | `10000` | Flush every N milliseconds |
35
+ | `maxPayloadBytes` | `number` | `460800` | Max payload bytes (450 KB) before triggering a flush |
36
+ | `maxQueueSize` | `number` | `10000` | Max events in ring buffer before dropping oldest |
37
+ | `timeout` | `number` | `10000` | Per-request timeout in milliseconds |
38
+ | `retryCount` | `number` | `3` | Max retries on transient failures |
39
+ | `disableCompression` | `boolean` | `false` | Disable gzip compression |
40
+ | `onError` | `(error: SdkError) => void` | `console.warn` | Called on errors — SDK never throws |
41
+ | `beforeSend` | `(event: SerializedEvent) => SerializedEvent \| null` | `undefined` | Per-event hook. Return `null` to drop the event |
42
+ | `beforeFlush` | `(batch: SerializedEvent[]) => SerializedEvent[] \| null` | `undefined` | Per-batch hook. Return `null` to drop the entire batch |
43
+ | `transport` | `CustomTransport` | `undefined` | Custom HTTP transport (for testing, mTLS, HTTP/2) |
44
+ | `offlineStore` | `OfflineStore` | `undefined` | Offline persistent store for failed batches |
45
+ | `maxConcurrentFlushes` | `number` | `30` | Max concurrent network promises |
46
+ | `shutdownTimeout` | `number` | `5000` | Shutdown timeout in milliseconds |
47
+
48
+ ## Environment variable overrides
49
+
50
+ All options can be overridden via environment variables. Env vars take precedence over code-level options.
51
+
52
+ | Variable | Overrides | Notes |
53
+ |----------|-----------|-------|
54
+ | `DOOW_TRACK_API_KEY` | first positional argument | Preferred over hard-coded key |
55
+ | `DOOW_TRACK_ENDPOINT` | `endpoint` | Override server URL |
56
+ | `DOOW_TRACK_DISABLED=true` | `enabled` | Disable the SDK entirely |
57
+ | `DOOW_TRACK_DEBUG=true` | `debug` | Enable debug output |
58
+ | `DOOW_TRACK_FLUSH_AT` | `flushAt` | Integer, e.g. `5` |
59
+ | `DOOW_TRACK_FLUSH_INTERVAL` | `flushInterval` | Milliseconds, e.g. `5000` |
60
+ | `DOOW_TRACK_ATTRIBUTION` | `attribution` | JSON string, e.g. `{"env":"prod"}` |
61
+
62
+ ## Serverless guide
63
+
64
+ Long-lived Node.js processes use the timer-based auto-flush. In serverless environments (Lambda, Vercel, Azure Functions) the process exits after each invocation, so you need guaranteed flush before return.
65
+
66
+ ### AWS Lambda
67
+
68
+ ```ts
69
+ import { DoowTracker } from '@doow/track';
70
+
71
+ const meter = new DoowTracker(process.env.DOOW_API_KEY!);
72
+
73
+ export const handler = meter.withLambda(async (event, context) => {
74
+ meter.track({ metric: 'api_calls', quantity: 1, license_id: 'lic_...' });
75
+ return { statusCode: 200, body: 'ok' };
76
+ // shutdown() is called automatically in a finally block
77
+ });
78
+ ```
79
+
80
+ ### Vercel
81
+
82
+ ```ts
83
+ import { DoowTracker } from '@doow/track';
84
+ import type { VercelRequest, VercelResponse } from '@vercel/node';
85
+
86
+ const meter = new DoowTracker(process.env.DOOW_API_KEY!);
87
+
88
+ export default meter.withVercel(async (req: VercelRequest, res: VercelResponse) => {
89
+ meter.track({ metric: 'requests', quantity: 1, license_id: 'lic_...' });
90
+ res.status(200).json({ ok: true });
91
+ });
92
+ ```
93
+
94
+ ### Azure Functions
95
+
96
+ ```ts
97
+ import { DoowTracker } from '@doow/track';
98
+ import type { Context } from '@azure/functions';
99
+
100
+ const meter = new DoowTracker(process.env.DOOW_API_KEY!);
101
+
102
+ export default meter.withAzureFunction(async (context: Context, req: unknown) => {
103
+ meter.track({ metric: 'executions', quantity: 1, license_id: 'lic_...' });
104
+ return { status: 200, body: 'ok' };
105
+ });
106
+ ```
107
+
108
+ ## Sidecar Docker Compose example
109
+
110
+ For use cases where you emit telemetry from non-Node.js services (Python, Go, Rust, etc.), run the sidecar container and pipe JSON events to it over stdin or TCP.
111
+
112
+ ```yaml
113
+ # docker-compose.yml
114
+ version: '3.9'
115
+
116
+ services:
117
+ app:
118
+ image: your-app
119
+ depends_on:
120
+ - doow-sidecar
121
+ environment:
122
+ - DOOW_SIDECAR_HOST=doow-sidecar
123
+ - DOOW_SIDECAR_PORT=9091
124
+
125
+ doow-sidecar:
126
+ image: doow/track-sidecar:latest
127
+ environment:
128
+ - DOOW_TRACK_API_KEY=dk_your_api_key
129
+ - DOOW_TRACK_ENDPOINT=https://api.doow.co
130
+ - DOOW_TRACK_INPUT=tcp # stdin | file-tail | tcp
131
+ - DOOW_TRACK_TCP_PORT=9091
132
+ - DOOW_TRACK_HEALTH_PORT=9090
133
+ ports:
134
+ - '9090:9090' # health check
135
+ - '9091:9091' # TCP event ingestion
136
+ healthcheck:
137
+ test: ['CMD', 'wget', '-qO-', 'http://localhost:9090/healthz']
138
+ interval: 10s
139
+ timeout: 5s
140
+ retries: 3
141
+ ```
142
+
143
+ Send events from your app as newline-delimited JSON:
144
+
145
+ ```json
146
+ {"metric":"api_calls","quantity":1,"license_id":"lic_..."}
147
+ {"metric":"tokens","quantity":512,"license_id":"lic_...","unit":"tokens"}
148
+ ```
149
+
150
+ ## CLI usage
151
+
152
+ Run the sidecar as a standalone daemon process:
153
+
154
+ ```bash
155
+ # Start as daemon with config file
156
+ npx @doow/track --config ./doow-track.json --pidfile /var/run/doow-track.pid
157
+
158
+ # Pipe mode: pipe newline-delimited JSON from stdin
159
+ echo '{"metric":"api_calls","quantity":1,"license_id":"lic_..."}' | npx @doow/track
160
+
161
+ # Reload config without restart (daemon mode)
162
+ kill -HUP $(cat /var/run/doow-track.pid)
163
+ ```
164
+
165
+ Config file (`doow-track.json`):
166
+
167
+ ```json
168
+ {
169
+ "api_key": "dk_your_api_key",
170
+ "endpoint": "https://api.doow.co",
171
+ "input": { "mode": "stdin" },
172
+ "flush_at": 20,
173
+ "flush_interval": 10000
174
+ }
175
+ ```
176
+
177
+ ### CLI flags
178
+
179
+ | Flag | Description |
180
+ |------|-------------|
181
+ | `--config <path>` | Path to JSON config file |
182
+ | `--api-key <key>` | API key (overrides config and env) |
183
+ | `--pidfile <path>` | Write PID to file (daemon mode) |
184
+ | `--version` | Print SDK version and exit |
185
+ | `--help` | Print usage and exit |
186
+
187
+ ## Offline store
188
+
189
+ When network delivery fails, events can be persisted locally and replayed on reconnect:
190
+
191
+ ```ts
192
+ import { DoowTracker, FileOfflineStore } from '@doow/track';
193
+
194
+ const meter = new DoowTracker('dk_your_api_key', {
195
+ offlineStore: new FileOfflineStore('./doow-track-offline'),
196
+ });
197
+ ```
198
+
199
+ Failed batches are written as atomic JSON files (write-then-rename) and replayed FIFO on the next successful flush.
200
+
201
+ ## Error handling
202
+
203
+ The SDK never throws. All errors are surfaced via the `onError` callback:
204
+
205
+ ```ts
206
+ const meter = new DoowTracker('dk_your_api_key', {
207
+ onError: (error) => {
208
+ console.error(`[doow/track] ${error.kind}: ${error.message}`);
209
+ // error.kind: 'AUTH_FAILURE' | 'RATE_LIMITED' | 'PARTIAL_ACCEPT' | 'NETWORK_ERROR' | 'TIMEOUT' | 'DROPPED_EVENTS' | 'TRANSPORT_ERROR'
210
+ },
211
+ });
212
+ ```
213
+
214
+ After `AUTH_FAILURE`, the SDK stops emitting permanently (check `meter.stopped`).
215
+
216
+ ## Further reading
217
+
218
+ - [Serverless guide](docs/serverless.md) — Lambda, Vercel, Azure Functions
219
+ - [Sidecar guide](docs/sidecar.md) — Docker Compose, Kubernetes sidecar pattern
220
+ - [Daemon / CLI guide](docs/daemon.md) — systemd unit file, config file reference
221
+ - [Migration guide](docs/migration.md) — Migrating from manual CSV upload to SDK
222
+ - [OTLP push guide](docs/otlp.md) — OpenTelemetry Collector config, GenAI semconv mapping
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ function createDebugLogger(enabled) {
4
+ if (!enabled) {
5
+ return { log: () => undefined, warn: () => undefined };
6
+ }
7
+ return {
8
+ log: (msg, ...args) => {
9
+ // eslint-disable-next-line no-console
10
+ console.warn(`[doow/track] ${msg}`, ...args);
11
+ },
12
+ warn: (msg, ...args) => {
13
+ console.warn(`[doow/track:warn] ${msg}`, ...args);
14
+ },
15
+ };
16
+ }
17
+
18
+ exports.createDebugLogger = createDebugLogger;
19
+ //# sourceMappingURL=debug.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug.js","sources":["../../../src/debug.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAgBM,SAAU,iBAAiB,CAAC,OAAgB,EAAA;IAMhD,IAAI,CAAC,OAAO,EAAE;AACZ,QAAA,OAAO,EAAE,GAAG,EAAE,MAAM,SAAS,EAAE,IAAI,EAAE,MAAM,SAAS,EAAE;IACxD;IAEA,OAAO;AACL,QAAA,GAAG,EAAE,CAAC,GAAW,EAAE,GAAG,IAAe,KAAU;;YAE7C,OAAO,CAAC,IAAI,CAAC,CAAA,aAAA,EAAgB,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC;QAC9C,CAAC;AACD,QAAA,IAAI,EAAE,CAAC,GAAW,EAAE,GAAG,IAAe,KAAU;YAC9C,OAAO,CAAC,IAAI,CAAC,CAAA,kBAAA,EAAqB,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC;QACnD,CAAC;KACF;AACH;;;;"}
@@ -0,0 +1,147 @@
1
+ 'use strict';
2
+
3
+ class EventProcessor {
4
+ constructor(config, exporter) {
5
+ this._queue = [];
6
+ this._timer = null;
7
+ this._hasFlushed = false;
8
+ this._currentBytes = 0;
9
+ /** Track pending async enqueue promises so shutdown can wait for them */
10
+ this._pendingEnqueues = new Set();
11
+ this._config = config;
12
+ this._exporter = exporter;
13
+ }
14
+ /** Enqueue an event. Applies beforeSend hook, ring buffer eviction, flush triggers. */
15
+ enqueue(event) {
16
+ const p = this._enqueueAsync(event);
17
+ this._pendingEnqueues.add(p);
18
+ void p.finally(() => this._pendingEnqueues.delete(p));
19
+ return p;
20
+ }
21
+ async _enqueueAsync(event) {
22
+ // Apply beforeSend hook
23
+ const processed = await this._applyBeforeSend(event);
24
+ if (processed === null) {
25
+ this._config.debug.log(`beforeSend dropped event ${event.event_id}`);
26
+ return;
27
+ }
28
+ // Ring buffer: evict oldest if at capacity
29
+ if (this._queue.length >= this._config.maxQueueSize) {
30
+ const evicted = this._queue.shift();
31
+ if (evicted) {
32
+ this._currentBytes -= this._eventBytes(evicted);
33
+ this._config.debug.warn(`Ring buffer full — evicted oldest event ${evicted.event_id}`);
34
+ }
35
+ }
36
+ this._queue.push(processed);
37
+ this._currentBytes += this._eventBytes(processed);
38
+ // First-event fast path
39
+ if (!this._hasFlushed) {
40
+ this._config.debug.log(`First event — immediate flush`);
41
+ await this._triggerFlush();
42
+ return;
43
+ }
44
+ // Count threshold
45
+ if (this._queue.length >= this._config.flushAt) {
46
+ this._config.debug.log(`Count threshold (${this._config.flushAt}) reached — flushing`);
47
+ await this._triggerFlush();
48
+ return;
49
+ }
50
+ // Byte threshold
51
+ if (this._currentBytes >= this._config.maxPayloadBytes) {
52
+ this._config.debug.log(`Byte threshold (${this._config.maxPayloadBytes}) reached — flushing`);
53
+ await this._triggerFlush();
54
+ return;
55
+ }
56
+ // Ensure timer is running
57
+ this._ensureTimer();
58
+ }
59
+ /** Manual flush — waits for pending enqueues, then flushes buffer */
60
+ async flush() {
61
+ if (this._pendingEnqueues.size > 0) {
62
+ await Promise.all([...this._pendingEnqueues]);
63
+ }
64
+ await this._triggerFlush();
65
+ }
66
+ /** Flush and stop timer */
67
+ async shutdown() {
68
+ this._stopTimer();
69
+ if (this._pendingEnqueues.size > 0) {
70
+ await Promise.all([...this._pendingEnqueues]);
71
+ }
72
+ await this._triggerFlush();
73
+ await this._exporter.drain();
74
+ }
75
+ /** Stop the interval timer */
76
+ _stopTimer() {
77
+ if (this._timer !== null) {
78
+ clearTimeout(this._timer);
79
+ this._timer = null;
80
+ }
81
+ }
82
+ _ensureTimer() {
83
+ if (this._timer !== null)
84
+ return;
85
+ this._timer = setTimeout(() => {
86
+ this._timer = null;
87
+ this._config.debug.log(`Timer flush after ${this._config.flushInterval}ms`);
88
+ void this._triggerFlush();
89
+ }, this._config.flushInterval);
90
+ }
91
+ async _triggerFlush() {
92
+ this._stopTimer();
93
+ if (this._queue.length === 0)
94
+ return;
95
+ // Drain the queue
96
+ const batch = this._queue.splice(0, this._queue.length);
97
+ this._currentBytes = 0;
98
+ this._hasFlushed = true;
99
+ // Apply beforeFlush hook
100
+ const finalBatch = await this._applyBeforeFlush(batch);
101
+ if (finalBatch === null || finalBatch.length === 0) {
102
+ this._config.debug.log(`beforeFlush dropped batch of ${batch.length} events`);
103
+ return;
104
+ }
105
+ await this._exporter.flush(finalBatch);
106
+ }
107
+ async _applyBeforeSend(event) {
108
+ if (!this._config.beforeSend)
109
+ return event;
110
+ try {
111
+ return await this._config.beforeSend(event);
112
+ }
113
+ catch (err) {
114
+ this._config.debug.warn(`beforeSend threw: ${String(err)}`);
115
+ return event; // fail-open
116
+ }
117
+ }
118
+ async _applyBeforeFlush(batch) {
119
+ if (!this._config.beforeFlush)
120
+ return batch;
121
+ try {
122
+ return await this._config.beforeFlush(batch);
123
+ }
124
+ catch (err) {
125
+ this._config.debug.warn(`beforeFlush threw: ${String(err)}`);
126
+ return batch; // fail-open
127
+ }
128
+ }
129
+ _eventBytes(event) {
130
+ return Buffer.byteLength(JSON.stringify(event), 'utf8');
131
+ }
132
+ /** Expose queue length for testing */
133
+ get queueLength() {
134
+ return this._queue.length;
135
+ }
136
+ /** Expose hasFlushed for testing */
137
+ get hasFlushed() {
138
+ return this._hasFlushed;
139
+ }
140
+ /** Override flushAt threshold (used by serverless wrappers to force flushAt=1) */
141
+ setFlushAt(n) {
142
+ this._config.flushAt = n;
143
+ }
144
+ }
145
+
146
+ exports.EventProcessor = EventProcessor;
147
+ //# sourceMappingURL=event-processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-processor.js","sources":["../../../src/event-processor.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}