@sploov/trace 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sploov
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 ADDED
@@ -0,0 +1,124 @@
1
+ # 🔍 Trace
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@sploov/trace.svg?style=flat-square)](https://www.npmjs.com/package/@sploov/trace)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square&logo=typescript)](https://www.typescriptlang.org/)
6
+
7
+ > **Execution flow, made visible.**
8
+
9
+ Trace is a lightweight, high-performance tracing and flow-visualization library for Node.js. Designed for developers who need to understand exactly what happens inside their async operations without the overhead of a full APM.
10
+
11
+ ---
12
+
13
+ ## ✨ Features
14
+
15
+ - 🚀 **Zero-Overhead Async Tracking**: Leverages `AsyncLocalStorage` for bulletproof context propagation.
16
+ - 🎨 **Visual Timelines**: Beautiful, colorized CLI output showing parallel and sequential execution.
17
+ - 🌲 **Nested Spans**: Effortlessly track parent-child relationships across complex async flows.
18
+ - 🛠 **Dev-Friendly**: Capture errors, add tags, and wrap functions with minimal boilerplate.
19
+ - 🔌 **Framework Ready**: First-class support for Express.js and easy integration for others.
20
+ - 💾 **Persistence**: Export traces to JSON/NDJSON and reload them for offline analysis.
21
+ - 🧩 **Extensible**: Hooks for custom exporters, metrics, and plugin support.
22
+
23
+ ---
24
+
25
+ ## 📦 Installation
26
+
27
+ ```bash
28
+ npm install @sploov/trace
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 🚀 Quick Start
34
+
35
+ ```typescript
36
+ import { trace } from '@sploov/trace';
37
+
38
+ async function performTask() {
39
+ await trace.startSpan('compute-data', async () => {
40
+ trace.tag('id', '12345');
41
+
42
+ // Simulate some work
43
+ await new Promise(r => setTimeout(r, 100));
44
+
45
+ console.log(trace.format());
46
+ });
47
+ }
48
+ ```
49
+
50
+ ### The Output
51
+
52
+ Trace transforms complex async logs into an intuitive timeline:
53
+
54
+ ```text
55
+ ● Trace ID: 87c2940c
56
+ ● Duration: 105ms
57
+ ● Spans: 1
58
+
59
+ Timeline Execution Flow
60
+ ┌────────────────────────────────────────┐
61
+ │━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━│ compute-data 105ms [id:12345]
62
+ └────────────────────────────────────────┘
63
+ ```
64
+
65
+ ---
66
+
67
+ ## 🛠 Advanced Usage
68
+
69
+ ### Function Wrapping
70
+ Don't want to change your function body? Wrap it!
71
+
72
+ ```typescript
73
+ const processUser = trace.wrap('process-user', (user) => {
74
+ // Automatically traced!
75
+ });
76
+ ```
77
+
78
+ ### Express Integration
79
+ Track entire request lifecycles with one line of code.
80
+
81
+ ```typescript
82
+ import { expressTrace } from '@sploov/trace/middleware';
83
+
84
+ app.use(expressTrace);
85
+ ```
86
+
87
+ ### Automatic Error Capture
88
+ Trace automatically catches errors and labels them in the timeline.
89
+
90
+ ```typescript
91
+ await trace.startSpan('risky-operation', async () => {
92
+ throw new Error('Something went wrong');
93
+ });
94
+ ```
95
+
96
+ ---
97
+
98
+ ## ⚙️ Configuration
99
+
100
+ Tune Trace to fit your production needs:
101
+
102
+ ```typescript
103
+ trace.configure({
104
+ enabled: true, // Toggle tracing globally
105
+ samplingRate: 0.05, // Only trace 5% of requests
106
+ maxSpans: 1000 // Protect memory in massive operations
107
+ });
108
+ ```
109
+
110
+ ---
111
+
112
+ ## 🤝 Contributing
113
+
114
+ We love contributions! Check out our [Contributing Guide](CONTRIBUTING.md) to get started.
115
+
116
+ ## 📄 License
117
+
118
+ Trace is [MIT Licensed](LICENSE).
119
+
120
+ ---
121
+
122
+ <p align="center">
123
+ Built with ❤️ for the developer community.
124
+ </p>
@@ -0,0 +1,22 @@
1
+ import type { SpanData, TraceData, TraceConfig, TracePlugin } from '../types.js';
2
+ export declare class TraceContext {
3
+ private storage;
4
+ private rootSpan;
5
+ private traceId;
6
+ private config;
7
+ private spanCount;
8
+ private isSampled;
9
+ private plugins;
10
+ configure(config: TraceConfig): void;
11
+ use(plugin: TracePlugin): void;
12
+ startSpan<T>(label: string, fn: (span: SpanData) => T): T;
13
+ wrap<Args extends any[], Ret>(label: string, fn: (...args: Args) => Ret): (...args: Args) => Ret;
14
+ tag(key: string, value: string | number | boolean): void;
15
+ get currentSpan(): SpanData | undefined;
16
+ private endSpan;
17
+ toJSON(): TraceData;
18
+ save(path: string): Promise<void>;
19
+ load(path: string): Promise<void>;
20
+ format(traceData?: TraceData): string;
21
+ }
22
+ //# sourceMappingURL=Trace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Trace.d.ts","sourceRoot":"","sources":["../../src/core/Trace.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGjF,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,OAAO,CAAwC;IACvD,OAAO,CAAC,MAAM,CAIZ;IACF,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,OAAO,CAAqB;IAEpC,SAAS,CAAC,MAAM,EAAE,WAAW;IAI7B,GAAG,CAAC,MAAM,EAAE,WAAW;IAIvB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,CAAC,GAAG,CAAC;IA2DzD,IAAI,CAAC,IAAI,SAAS,GAAG,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,GAAG,GAAG,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,GAAG;IAIhG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI;IAKxD,IAAI,WAAW,IAAI,QAAQ,GAAG,SAAS,CAEtC;IAED,OAAO,CAAC,OAAO;IAYf,MAAM,IAAI,SAAS;IASb,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASvC,MAAM,CAAC,SAAS,GAAE,SAAyB,GAAG,MAAM;CAGrD"}
@@ -0,0 +1,122 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { writeFile, readFile } from 'node:fs/promises';
4
+ import { Formatter } from '../ui/Formatter.js';
5
+ export class TraceContext {
6
+ storage = new AsyncLocalStorage();
7
+ rootSpan = null;
8
+ traceId = randomUUID().substring(0, 8);
9
+ config = {
10
+ enabled: process.env['TRACE_ENABLED'] !== 'false',
11
+ samplingRate: 1.0,
12
+ maxSpans: 1000
13
+ };
14
+ spanCount = 0;
15
+ isSampled = true;
16
+ plugins = [];
17
+ configure(config) {
18
+ this.config = { ...this.config, ...config };
19
+ }
20
+ use(plugin) {
21
+ this.plugins.push(plugin);
22
+ }
23
+ startSpan(label, fn) {
24
+ if (!this.config.enabled)
25
+ return fn({});
26
+ const parentSpan = this.storage.getStore();
27
+ if (!parentSpan) {
28
+ this.isSampled = Math.random() < this.config.samplingRate;
29
+ this.spanCount = 0;
30
+ this.rootSpan = null;
31
+ this.traceId = randomUUID().substring(0, 8);
32
+ if (this.isSampled) {
33
+ this.plugins.forEach(p => p.onTraceStart?.(this.traceId));
34
+ }
35
+ }
36
+ if (!this.isSampled)
37
+ return fn({});
38
+ if (this.spanCount >= this.config.maxSpans) {
39
+ return fn({});
40
+ }
41
+ this.spanCount++;
42
+ const span = {
43
+ id: randomUUID().substring(0, 8),
44
+ parentId: parentSpan ? parentSpan.id : null,
45
+ label,
46
+ startTime: Date.now(),
47
+ tags: {},
48
+ children: []
49
+ };
50
+ if (parentSpan) {
51
+ parentSpan.children.push(span);
52
+ }
53
+ else {
54
+ this.rootSpan = span;
55
+ }
56
+ this.plugins.forEach(p => p.onSpanStart?.(span));
57
+ return this.storage.run(span, () => {
58
+ try {
59
+ const result = fn(span);
60
+ if (result instanceof Promise) {
61
+ return result
62
+ .catch(err => {
63
+ span.error = err;
64
+ throw err;
65
+ })
66
+ .finally(() => this.endSpan(span));
67
+ }
68
+ this.endSpan(span);
69
+ return result;
70
+ }
71
+ catch (error) {
72
+ span.error = error;
73
+ this.endSpan(span);
74
+ throw error;
75
+ }
76
+ });
77
+ }
78
+ wrap(label, fn) {
79
+ return (...args) => this.startSpan(label, () => fn(...args));
80
+ }
81
+ tag(key, value) {
82
+ const span = this.storage.getStore();
83
+ if (span)
84
+ span.tags[key] = value;
85
+ }
86
+ get currentSpan() {
87
+ return this.storage.getStore();
88
+ }
89
+ endSpan(span) {
90
+ if (span.id && span.endTime === undefined) {
91
+ span.endTime = Date.now();
92
+ span.duration = span.endTime - span.startTime;
93
+ this.plugins.forEach(p => p.onSpanEnd?.(span));
94
+ if (span === this.rootSpan) {
95
+ this.plugins.forEach(p => p.onTraceEnd?.(this.toJSON()));
96
+ }
97
+ }
98
+ }
99
+ toJSON() {
100
+ return {
101
+ id: this.traceId,
102
+ rootSpan: this.rootSpan,
103
+ isSampled: this.isSampled,
104
+ spanCount: this.spanCount
105
+ };
106
+ }
107
+ async save(path) {
108
+ await writeFile(path, JSON.stringify(this.toJSON(), null, 2));
109
+ }
110
+ async load(path) {
111
+ const data = await readFile(path, 'utf-8');
112
+ const parsed = JSON.parse(data);
113
+ this.traceId = parsed.id;
114
+ this.rootSpan = parsed.rootSpan;
115
+ this.isSampled = parsed.isSampled ?? true;
116
+ this.spanCount = parsed.spanCount ?? 0;
117
+ }
118
+ format(traceData = this.toJSON()) {
119
+ return Formatter.format(traceData);
120
+ }
121
+ }
122
+ //# sourceMappingURL=Trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Trace.js","sourceRoot":"","sources":["../../src/core/Trace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=example.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example.d.ts","sourceRoot":"","sources":["../src/example.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ import { trace } from './index.js';
2
+ async function simulate() {
3
+ await trace.startSpan('root-request', async () => {
4
+ trace.tag('api_version', 'v1');
5
+ await trace.startSpan('authentication', async () => {
6
+ await new Promise(r => setTimeout(r, 20));
7
+ trace.tag('user_id', 'user_123');
8
+ });
9
+ await Promise.all([
10
+ trace.startSpan('fetch-db', async () => {
11
+ await new Promise(r => setTimeout(r, 60));
12
+ trace.tag('db.query', 'SELECT * FROM users');
13
+ }),
14
+ trace.startSpan('fetch-cache', async () => {
15
+ await new Promise(r => setTimeout(r, 15));
16
+ })
17
+ ]);
18
+ try {
19
+ await trace.startSpan('legacy-service', async () => {
20
+ await new Promise(r => setTimeout(r, 10));
21
+ throw new Error('Service Unavailable');
22
+ });
23
+ }
24
+ catch (e) {
25
+ // handled
26
+ }
27
+ await trace.startSpan('serialization', async () => {
28
+ await new Promise(r => setTimeout(r, 5));
29
+ });
30
+ console.log(trace.format());
31
+ });
32
+ }
33
+ simulate();
34
+ //# sourceMappingURL=example.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example.js","sourceRoot":"","sources":["../src/example.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,KAAK,UAAU,QAAQ;IACrB,MAAM,KAAK,CAAC,SAAS,CAAC,cAAc,EAAE,KAAK,IAAI,EAAE;QAC/C,KAAK,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QAE/B,MAAM,KAAK,CAAC,SAAS,CAAC,gBAAgB,EAAE,KAAK,IAAI,EAAE;YACjD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAC1C,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,CAAC,GAAG,CAAC;YAChB,KAAK,CAAC,SAAS,CAAC,UAAU,EAAE,KAAK,IAAI,EAAE;gBACrC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC1C,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC;YAC/C,CAAC,CAAC;YACF,KAAK,CAAC,SAAS,CAAC,aAAa,EAAE,KAAK,IAAI,EAAE;gBACxC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAC5C,CAAC,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,SAAS,CAAC,gBAAgB,EAAE,KAAK,IAAI,EAAE;gBACjD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC1C,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;YACzC,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,UAAU;QACZ,CAAC;QAED,MAAM,KAAK,CAAC,SAAS,CAAC,eAAe,EAAE,KAAK,IAAI,EAAE;YAChD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { TraceContext } from './core/Trace.js';
2
+ export * from './types.js';
3
+ export * from './core/Trace.js';
4
+ export * from './ui/Formatter.js';
5
+ export * from './integrations/Express.js';
6
+ export declare const trace: TraceContext;
7
+ export default trace;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAE1C,eAAO,MAAM,KAAK,cAAqB,CAAC;AACxC,eAAe,KAAK,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ import { TraceContext } from './core/Trace.js';
2
+ export * from './types.js';
3
+ export * from './core/Trace.js';
4
+ export * from './ui/Formatter.js';
5
+ export * from './integrations/Express.js';
6
+ export const trace = new TraceContext();
7
+ export default trace;
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAE1C,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,YAAY,EAAE,CAAC;AACxC,eAAe,KAAK,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ /**
3
+ * Express middleware for Trace
4
+ */
5
+ export declare function expressTrace(req: Request, res: Response, next: NextFunction): void;
6
+ //# sourceMappingURL=Express.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Express.d.ts","sourceRoot":"","sources":["../../src/integrations/Express.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG/D;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,QAmB3E"}
@@ -0,0 +1,21 @@
1
+ import { trace } from '../index.js';
2
+ /**
3
+ * Express middleware for Trace
4
+ */
5
+ export function expressTrace(req, res, next) {
6
+ const label = `${req.method} ${req.path}`;
7
+ trace.startSpan(label, async () => {
8
+ trace.tag('http.method', req.method);
9
+ trace.tag('http.path', req.path);
10
+ const responseFinished = new Promise((resolve) => {
11
+ res.on('finish', () => {
12
+ trace.tag('http.status', res.statusCode);
13
+ resolve();
14
+ });
15
+ res.on('close', resolve);
16
+ });
17
+ next();
18
+ await responseFinished;
19
+ });
20
+ }
21
+ //# sourceMappingURL=Express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Express.js","sourceRoot":"","sources":["../../src/integrations/Express.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;IAC1E,MAAM,KAAK,GAAG,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;IAE1C,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;QAChC,KAAK,CAAC,GAAG,CAAC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QACrC,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QAEjC,MAAM,gBAAgB,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACrD,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACpB,KAAK,CAAC,GAAG,CAAC,aAAa,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;gBACzC,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,IAAI,EAAE,CAAC;QAEP,MAAM,gBAAgB,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/test.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":""}
package/dist/test.js ADDED
@@ -0,0 +1,54 @@
1
+ import { trace } from './index.js';
2
+ import assert from 'node:assert';
3
+ async function testBasicTracing() {
4
+ console.log('Testing basic tracing...');
5
+ await trace.startSpan('root', async () => {
6
+ trace.tag('test', 'basic');
7
+ await trace.startSpan('child', async () => {
8
+ await new Promise(r => setTimeout(r, 10));
9
+ });
10
+ });
11
+ const data = trace.toJSON();
12
+ assert.strictEqual(data.rootSpan?.label, 'root');
13
+ assert.strictEqual(data.rootSpan?.children.length, 1);
14
+ assert.strictEqual(data.rootSpan?.children[0]?.label, 'child');
15
+ assert.strictEqual(data.rootSpan?.tags['test'], 'basic');
16
+ console.log('✅ Basic tracing passed');
17
+ }
18
+ async function testErrorCapturing() {
19
+ console.log('Testing error capturing...');
20
+ try {
21
+ await trace.startSpan('error-parent', async () => {
22
+ throw new Error('boom');
23
+ });
24
+ }
25
+ catch (e) { }
26
+ const data = trace.toJSON();
27
+ assert.strictEqual(data.rootSpan?.label, 'error-parent');
28
+ assert.strictEqual(data.rootSpan?.error?.message, 'boom');
29
+ console.log('✅ Error capturing passed');
30
+ }
31
+ async function testSampling() {
32
+ console.log('Testing sampling...');
33
+ trace.configure({ samplingRate: 0 });
34
+ await trace.startSpan('not-sampled', () => { });
35
+ assert.strictEqual(trace.format(), '\u001b[33m⚠ Trace not sampled.\u001b[39m');
36
+ trace.configure({ samplingRate: 1 });
37
+ await trace.startSpan('sampled', () => { });
38
+ assert.notStrictEqual(trace.format(), '\u001b[33m⚠ Trace not sampled.\u001b[39m');
39
+ console.log('✅ Sampling passed');
40
+ }
41
+ async function runTests() {
42
+ try {
43
+ await testBasicTracing();
44
+ await testErrorCapturing();
45
+ await testSampling();
46
+ console.log('\nAll tests passed! 🚀');
47
+ }
48
+ catch (error) {
49
+ console.error('❌ Test failed:', error);
50
+ process.exit(1);
51
+ }
52
+ }
53
+ runTests();
54
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.js","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,KAAK,UAAU,gBAAgB;IAC7B,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;IACxC,MAAM,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;QACvC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC3B,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;YACxC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;IAC5B,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACjD,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACtD,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAC/D,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AACxC,CAAC;AAED,KAAK,UAAU,kBAAkB;IAC/B,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,SAAS,CAAC,cAAc,EAAE,KAAK,IAAI,EAAE;YAC/C,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,CAAA,CAAC;IAEd,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;IAC5B,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;IACzD,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;AAC1C,CAAC;AAED,KAAK,UAAU,YAAY;IACzB,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACnC,KAAK,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;IACrC,MAAM,KAAK,CAAC,SAAS,CAAC,aAAa,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC/C,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,0CAA0C,CAAC,CAAC;IAE/E,KAAK,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;IACrC,MAAM,KAAK,CAAC,SAAS,CAAC,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC3C,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,0CAA0C,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;AACnC,CAAC;AAED,KAAK,UAAU,QAAQ;IACrB,IAAI,CAAC;QACH,MAAM,gBAAgB,EAAE,CAAC;QACzB,MAAM,kBAAkB,EAAE,CAAC;QAC3B,MAAM,YAAY,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,29 @@
1
+ export interface SpanData {
2
+ id: string;
3
+ parentId: string | null;
4
+ label: string;
5
+ startTime: number;
6
+ endTime?: number;
7
+ duration?: number;
8
+ error?: any;
9
+ tags: Record<string, string | number | boolean>;
10
+ children: SpanData[];
11
+ }
12
+ export interface TraceData {
13
+ id: string;
14
+ rootSpan: SpanData | null;
15
+ isSampled: boolean;
16
+ spanCount: number;
17
+ }
18
+ export interface TraceConfig {
19
+ enabled?: boolean;
20
+ samplingRate?: number;
21
+ maxSpans?: number;
22
+ }
23
+ export interface TracePlugin {
24
+ onTraceStart?: (id: string) => void;
25
+ onTraceEnd?: (data: TraceData) => void;
26
+ onSpanStart?: (span: SpanData) => void;
27
+ onSpanEnd?: (span: SpanData) => void;
28
+ }
29
+ //# 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,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAChD,QAAQ,EAAE,QAAQ,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACvC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;CACtC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import type { TraceData } from '../types.js';
2
+ export declare class Formatter {
3
+ private static readonly BAR_WIDTH;
4
+ static format(traceData: TraceData): string;
5
+ private static renderSpan;
6
+ }
7
+ //# sourceMappingURL=Formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Formatter.d.ts","sourceRoot":"","sources":["../../src/ui/Formatter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,aAAa,CAAC;AAEvD,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAM;IAEvC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM;IA8B3C,OAAO,CAAC,MAAM,CAAC,UAAU;CA0C1B"}
@@ -0,0 +1,61 @@
1
+ import pc from 'picocolors';
2
+ export class Formatter {
3
+ static BAR_WIDTH = 40;
4
+ static format(traceData) {
5
+ if (!traceData.isSampled)
6
+ return pc.yellow('⚠ Trace not sampled.');
7
+ const { id, rootSpan, spanCount } = traceData;
8
+ if (!rootSpan)
9
+ return pc.red('✖ No trace recorded.');
10
+ const now = Date.now();
11
+ const totalDuration = (rootSpan.duration || (now - rootSpan.startTime)) || 1;
12
+ const rootStart = rootSpan.startTime;
13
+ let output = `
14
+ ${pc.bold(pc.cyan('●'))} ${pc.bold('Trace ID:')} ${pc.gray(id)}
15
+ `;
16
+ output += ` ${pc.bold(pc.cyan('●'))} ${pc.bold('Duration:')} ${pc.blue(`${totalDuration}ms`)}
17
+ `;
18
+ output += ` ${pc.bold(pc.cyan('●'))} ${pc.bold('Spans: ')} ${pc.gray(spanCount.toString())}
19
+
20
+ `;
21
+ const barHeader = ' '.repeat(2) + pc.dim('Timeline') + ' '.repeat(this.BAR_WIDTH - 8 + 2) + pc.dim('Execution Flow') + '\n';
22
+ output += barHeader;
23
+ output += ` ${pc.gray('┌' + '─'.repeat(this.BAR_WIDTH) + '┐')}\n`;
24
+ output += this.renderSpan(rootSpan, 0, rootStart, totalDuration);
25
+ output += ` ${pc.gray('└' + '─'.repeat(this.BAR_WIDTH) + '┘')}\n`;
26
+ return output;
27
+ }
28
+ static renderSpan(span, depth, rootStart, totalDuration) {
29
+ const now = Date.now();
30
+ const relativeStart = span.startTime - rootStart;
31
+ const duration = span.duration || (now - span.startTime);
32
+ const startPos = Math.max(0, Math.floor((relativeStart / totalDuration) * this.BAR_WIDTH));
33
+ let barWidth = Math.max(1, Math.floor((duration / totalDuration) * this.BAR_WIDTH));
34
+ // Ensure bar doesn't exceed total width
35
+ if (startPos + barWidth > this.BAR_WIDTH) {
36
+ barWidth = this.BAR_WIDTH - startPos;
37
+ }
38
+ const isError = !!span.error;
39
+ const barColor = isError ? pc.red : (depth === 0 ? pc.cyan : (depth % 2 === 0 ? pc.green : pc.yellow));
40
+ const barContent = '━'.repeat(Math.max(0, barWidth));
41
+ const bar = ' '.repeat(startPos) + barColor(barContent);
42
+ const paddedBar = bar.padEnd(this.BAR_WIDTH + (isError ? 10 : 10)); // ANSI compensation
43
+ const indent = depth > 0 ? pc.gray(' '.repeat(depth - 1) + '└─ ') : '';
44
+ const label = isError ? pc.red(pc.bold(span.label)) : (depth === 0 ? pc.bold(pc.white(span.label)) : pc.white(span.label));
45
+ const durationStr = pc.dim(`${duration}ms`);
46
+ const tags = Object.keys(span.tags).length
47
+ ? ` ${pc.dim(`[${Object.entries(span.tags).map(([k, v]) => `${pc.cyan(k)}:${v}`).join(', ')}]`)}`
48
+ : '';
49
+ const errorMsg = span.error ? ` ${pc.bgRed(pc.white(pc.bold(' ERROR ')))} ${pc.red(span.error.message || span.error)}` : '';
50
+ // We need to carefully handle the padding because of ANSI codes
51
+ // Re-calculating visible length for the bar part
52
+ const visibleBar = ' '.repeat(startPos) + '━'.repeat(Math.max(0, barWidth));
53
+ const barPadding = ' '.repeat(this.BAR_WIDTH - visibleBar.length);
54
+ let line = ` ${pc.gray('│')}${bar}${barPadding}${pc.gray('│')} ${indent}${label} ${durationStr}${tags}${errorMsg}\n`;
55
+ for (const child of span.children) {
56
+ line += this.renderSpan(child, depth + 1, rootStart, totalDuration);
57
+ }
58
+ return line;
59
+ }
60
+ }
61
+ //# sourceMappingURL=Formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Formatter.js","sourceRoot":"","sources":["../../src/ui/Formatter.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@sploov/trace",
3
+ "version": "1.0.0",
4
+ "description": "Trace is a lightweight execution tracing and flow-visualization tool that helps developers understand how code actually runs across time, state, and events.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": "./dist/index.js",
9
+ "./middleware": "./dist/integrations/Express.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "start": "node dist/index.js",
14
+ "dev": "ts-node src/index.ts",
15
+ "test": "echo \"Error: no test specified\" && exit 1"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/sploov/Trace.git"
20
+ },
21
+ "keywords": [
22
+ "trace",
23
+ "tracing",
24
+ "telemetry",
25
+ "visualization",
26
+ "async-safe",
27
+ "performance",
28
+ "node",
29
+ "typescript"
30
+ ],
31
+ "author": "Sploov",
32
+ "license": "MIT",
33
+ "type": "module",
34
+ "bugs": {
35
+ "url": "https://github.com/sploov/Trace/issues"
36
+ },
37
+ "homepage": "https://github.com/sploov/Trace#readme",
38
+ "devDependencies": {
39
+ "@types/express": "^5.0.6",
40
+ "@types/node": "^25.0.3",
41
+ "ts-node": "^10.9.2",
42
+ "tsx": "^4.21.0",
43
+ "typescript": "^5.9.3"
44
+ },
45
+ "dependencies": {
46
+ "express": "^5.2.1",
47
+ "picocolors": "^1.1.1"
48
+ }
49
+ }