@detiq/crawler 1.1.9 → 1.1.10

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.
Files changed (82) hide show
  1. package/dist/accessibility-auditor.js +54 -74
  2. package/dist/ai-login.js +227 -249
  3. package/dist/auth-token-rotator.js +57 -67
  4. package/dist/auth.js +299 -336
  5. package/dist/browser-extension.js +104 -142
  6. package/dist/captcha-solver.js +98 -125
  7. package/dist/color-contrast.js +77 -89
  8. package/dist/content-chunker.js +126 -126
  9. package/dist/content-dedup.js +54 -71
  10. package/dist/cookie-consent.js +68 -83
  11. package/dist/cookie-jar.js +53 -60
  12. package/dist/crawler.js +4494 -5353
  13. package/dist/custom-js-injector.js +45 -61
  14. package/dist/dark-mode-capture.js +67 -81
  15. package/dist/dead-link-detector.js +47 -55
  16. package/dist/element-hash.js +8 -12
  17. package/dist/email-otp.js +154 -195
  18. package/dist/engine-router.js +87 -98
  19. package/dist/export/bulk-export.js +70 -55
  20. package/dist/export/vector-export.js +77 -105
  21. package/dist/extractors/advanced.js +249 -266
  22. package/dist/extractors/artifact-envelope.js +59 -58
  23. package/dist/failure-analysis-agent.js +37 -33
  24. package/dist/feature-flag-detector.js +51 -66
  25. package/dist/form-detector.js +55 -75
  26. package/dist/frontier.js +76 -101
  27. package/dist/gap-pass.js +68 -89
  28. package/dist/graphql-detector.js +62 -72
  29. package/dist/http-engine.js +282 -330
  30. package/dist/incremental-diff.js +54 -87
  31. package/dist/index.js +67 -28
  32. package/dist/input-fuzzer.js +93 -97
  33. package/dist/interactive-probe.js +715 -827
  34. package/dist/job-queue.js +2151 -2169
  35. package/dist/js-coverage.js +52 -56
  36. package/dist/js-router-detector.js +86 -117
  37. package/dist/language-detector.js +51 -63
  38. package/dist/link-classifier.js +32 -50
  39. package/dist/live-view.js +91 -108
  40. package/dist/locator-extractor.js +151 -187
  41. package/dist/logger.js +9 -7
  42. package/dist/markdown-extract.js +48 -61
  43. package/dist/native-crawler.js +780 -946
  44. package/dist/network-observer.js +135 -156
  45. package/dist/network-throttle.js +29 -40
  46. package/dist/openapi-generator.js +77 -92
  47. package/dist/pdf-extractor.js +72 -90
  48. package/dist/performance-metrics.js +80 -102
  49. package/dist/phash.js +125 -144
  50. package/dist/pii-scrubber.js +65 -68
  51. package/dist/playwright-recorder.js +70 -58
  52. package/dist/policies/crawl-safety.js +31 -34
  53. package/dist/profiles/domain-profile.js +130 -125
  54. package/dist/proxy.js +146 -172
  55. package/dist/rate-limiter.js +58 -65
  56. package/dist/redirect-tracker.js +60 -64
  57. package/dist/redis-frontier.js +116 -136
  58. package/dist/redis-queue.js +157 -144
  59. package/dist/response-cache.js +127 -140
  60. package/dist/responsive-detector.js +42 -46
  61. package/dist/retry-policy.js +46 -50
  62. package/dist/robots-txt.js +69 -83
  63. package/dist/screen-reader.js +45 -70
  64. package/dist/screenshot-masker.js +59 -77
  65. package/dist/seo-checker.js +88 -107
  66. package/dist/session-capture.js +192 -212
  67. package/dist/session-export.js +107 -133
  68. package/dist/sitemap-writer.js +38 -41
  69. package/dist/sitemap.js +141 -202
  70. package/dist/skyvern.js +123 -148
  71. package/dist/sqs-queue.js +42 -42
  72. package/dist/sso-token.js +74 -90
  73. package/dist/stealth/human-behavior.js +156 -173
  74. package/dist/telemetry.js +68 -82
  75. package/dist/test-executor.js +309 -335
  76. package/dist/tls-fingerprint.js +77 -88
  77. package/dist/url-pattern-completer.js +71 -90
  78. package/dist/visual-diff-verdict.js +23 -38
  79. package/dist/webhook-notifier.js +71 -69
  80. package/dist/websocket-detector.js +56 -68
  81. package/dist/worker.js +29 -38
  82. package/package.json +1 -1
@@ -1,186 +1,169 @@
1
- // Gaussian random using Box-Muller transform
2
1
  function gaussian(mean, sigma) {
3
- const u1 = Math.random();
4
- const u2 = Math.random();
5
- const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
6
- return mean + z * sigma;
2
+ const u1 = Math.random();
3
+ const u2 = Math.random();
4
+ const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
5
+ return mean + z * sigma;
7
6
  }
8
- export async function humanDelay(min = 80, max = 450) {
9
- const mean = (min + max) / 2;
10
- const sigma = (max - min) / 6;
11
- const delay = Math.max(min, Math.min(max, gaussian(mean, sigma)));
12
- await new Promise(r => setTimeout(r, Math.round(delay)));
7
+ async function humanDelay(min = 80, max = 450) {
8
+ const mean = (min + max) / 2;
9
+ const sigma = (max - min) / 6;
10
+ const delay = Math.max(min, Math.min(max, gaussian(mean, sigma)));
11
+ await new Promise((r) => setTimeout(r, Math.round(delay)));
13
12
  }
14
- export async function simulateMouseMovement(page, targetX, targetY) {
15
- try {
16
- const mouse = page.mouse;
17
- const steps = 8 + Math.floor(Math.random() * 8);
18
- // Generate Bezier control points with slight perpendicular offset
19
- const startX = targetX - 100 + Math.random() * 200;
20
- const startY = targetY - 100 + Math.random() * 200;
21
- const cpX = (startX + targetX) / 2 + (Math.random() - 0.5) * 80;
22
- const cpY = (startY + targetY) / 2 + (Math.random() - 0.5) * 80;
23
- for (let i = 0; i <= steps; i++) {
24
- const t = i / steps;
25
- // Quadratic Bezier: B(t) = (1-t)^2 * P0 + 2(1-t)t * P1 + t^2 * P2
26
- const x = (1 - t) * (1 - t) * startX + 2 * (1 - t) * t * cpX + t * t * targetX;
27
- const y = (1 - t) * (1 - t) * startY + 2 * (1 - t) * t * cpY + t * t * targetY;
28
- await mouse.move(x + (Math.random() - 0.5) * 2, y + (Math.random() - 0.5) * 2);
29
- if (i < steps)
30
- await humanDelay(8, 25);
31
- }
32
- // Small overshoot correction
33
- await mouse.move(targetX + (Math.random() - 0.5) * 4, targetY + (Math.random() - 0.5) * 4);
34
- await humanDelay(30, 80);
35
- await mouse.move(targetX, targetY);
13
+ async function simulateMouseMovement(page, targetX, targetY) {
14
+ try {
15
+ const mouse = page.mouse;
16
+ const steps = 8 + Math.floor(Math.random() * 8);
17
+ const startX = targetX - 100 + Math.random() * 200;
18
+ const startY = targetY - 100 + Math.random() * 200;
19
+ const cpX = (startX + targetX) / 2 + (Math.random() - 0.5) * 80;
20
+ const cpY = (startY + targetY) / 2 + (Math.random() - 0.5) * 80;
21
+ for (let i = 0; i <= steps; i++) {
22
+ const t = i / steps;
23
+ const x = (1 - t) * (1 - t) * startX + 2 * (1 - t) * t * cpX + t * t * targetX;
24
+ const y = (1 - t) * (1 - t) * startY + 2 * (1 - t) * t * cpY + t * t * targetY;
25
+ await mouse.move(x + (Math.random() - 0.5) * 2, y + (Math.random() - 0.5) * 2);
26
+ if (i < steps) await humanDelay(8, 25);
36
27
  }
37
- catch { /* non-fatal */ }
28
+ await mouse.move(targetX + (Math.random() - 0.5) * 4, targetY + (Math.random() - 0.5) * 4);
29
+ await humanDelay(30, 80);
30
+ await mouse.move(targetX, targetY);
31
+ } catch {
32
+ }
38
33
  }
39
- export async function simulateMouseHover(page, selector) {
40
- try {
41
- const el = await page.$(selector);
42
- if (!el)
43
- return;
44
- const box = await el.boundingBox();
45
- if (!box)
46
- return;
47
- const cx = box.x + box.width / 2;
48
- const cy = box.y + box.height / 2;
49
- await simulateMouseMovement(page, cx, cy);
50
- await humanDelay(100, 300);
51
- }
52
- catch { /* non-fatal */ }
34
+ async function simulateMouseHover(page, selector) {
35
+ try {
36
+ const el = await page.$(selector);
37
+ if (!el) return;
38
+ const box = await el.boundingBox();
39
+ if (!box) return;
40
+ const cx = box.x + box.width / 2;
41
+ const cy = box.y + box.height / 2;
42
+ await simulateMouseMovement(page, cx, cy);
43
+ await humanDelay(100, 300);
44
+ } catch {
45
+ }
53
46
  }
54
- export async function simulateClick(page, selector) {
55
- try {
56
- await simulateMouseHover(page, selector);
57
- await humanDelay(50, 150);
58
- await page.click(selector, { delay: Math.round(gaussian(80, 30)) });
59
- }
60
- catch { /* non-fatal */ }
47
+ async function simulateClick(page, selector) {
48
+ try {
49
+ await simulateMouseHover(page, selector);
50
+ await humanDelay(50, 150);
51
+ await page.click(selector, { delay: Math.round(gaussian(80, 30)) });
52
+ } catch {
53
+ }
61
54
  }
62
- export async function injectFingerprintOverrides(page) {
63
- await page.addInitScript(() => {
64
- // Canvas noise — subtle per-pixel perturbation
65
- const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
66
- HTMLCanvasElement.prototype.toDataURL = function (...args) {
67
- const ctx = this.getContext('2d');
68
- if (ctx) {
69
- const imageData = ctx.getImageData(0, 0, this.width, this.height);
70
- for (let i = 0; i < imageData.data.length; i += 4) {
71
- imageData.data[i] = Math.max(0, Math.min(255, imageData.data[i] + (Math.random() < 0.01 ? (Math.random() > 0.5 ? 1 : -1) : 0)));
72
- }
73
- ctx.putImageData(imageData, 0, 0);
74
- }
75
- return origToDataURL.apply(this, args);
76
- };
77
- // WebGL parameter spoofing
78
- const origGetParam = WebGLRenderingContext.prototype.getParameter;
79
- WebGLRenderingContext.prototype.getParameter = function (parameter) {
80
- const UNMASKED_VENDOR_WEBGL = 0x9245;
81
- const UNMASKED_RENDERER_WEBGL = 0x9246;
82
- if (parameter === UNMASKED_VENDOR_WEBGL)
83
- return 'Intel Inc.';
84
- if (parameter === UNMASKED_RENDERER_WEBGL)
85
- return 'Intel Iris OpenGL Engine';
86
- return origGetParam.call(this, parameter);
87
- };
88
- // Hardware concurrency — pick from common values
89
- const cpuCounts = [4, 6, 8, 8];
90
- Object.defineProperty(navigator, 'hardwareConcurrency', {
91
- get: () => cpuCounts[Math.floor(Math.random() * cpuCounts.length)],
92
- configurable: true,
93
- });
94
- // Device memory
95
- Object.defineProperty(navigator, 'deviceMemory', {
96
- get: () => [4, 8][Math.floor(Math.random() * 2)],
97
- configurable: true,
98
- });
99
- }).catch(() => { });
100
- }
101
- export async function infiniteScrollToBottom(page, opts = {}) {
102
- const { maxScrolls = 50, scrollDelay = 400, stabilizeMs = 600 } = opts;
103
- let scrollCount = 0;
104
- let stableScrollCount = 0;
105
- let previousHeight = 0;
106
- try {
107
- while (scrollCount < maxScrolls) {
108
- previousHeight = await page.evaluate(() => document.documentElement.scrollHeight).catch(() => 0);
109
- const scrollBy = 500 + Math.floor(Math.random() * 300);
110
- await page.evaluate((by) => window.scrollBy(0, by), scrollBy).catch(() => { });
111
- await humanDelay(scrollDelay - 100, scrollDelay + 200);
112
- // Wait for potential lazy-loaded content
113
- await page.waitForTimeout(stabilizeMs).catch(() => { });
114
- scrollCount++;
115
- const newHeight = await page.evaluate(() => document.documentElement.scrollHeight).catch(() => 0);
116
- if (newHeight - previousHeight < 10) {
117
- stableScrollCount++;
118
- if (stableScrollCount >= 3)
119
- break; // no meaningful new content after 3 consecutive non-growing scrolls
120
- }
121
- else {
122
- stableScrollCount = 0;
123
- }
124
- previousHeight = newHeight;
55
+ async function injectFingerprintOverrides(page) {
56
+ await page.addInitScript(() => {
57
+ const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
58
+ HTMLCanvasElement.prototype.toDataURL = function(...args) {
59
+ const ctx = this.getContext("2d");
60
+ if (ctx) {
61
+ const imageData = ctx.getImageData(0, 0, this.width, this.height);
62
+ for (let i = 0; i < imageData.data.length; i += 4) {
63
+ imageData.data[i] = Math.max(0, Math.min(255, imageData.data[i] + (Math.random() < 0.01 ? Math.random() > 0.5 ? 1 : -1 : 0)));
125
64
  }
126
- }
127
- catch { /* non-fatal */ }
128
- return { scrollCount, heightReached: previousHeight };
65
+ ctx.putImageData(imageData, 0, 0);
66
+ }
67
+ return origToDataURL.apply(this, args);
68
+ };
69
+ const origGetParam = WebGLRenderingContext.prototype.getParameter;
70
+ WebGLRenderingContext.prototype.getParameter = function(parameter) {
71
+ const UNMASKED_VENDOR_WEBGL = 37445;
72
+ const UNMASKED_RENDERER_WEBGL = 37446;
73
+ if (parameter === UNMASKED_VENDOR_WEBGL) return "Intel Inc.";
74
+ if (parameter === UNMASKED_RENDERER_WEBGL) return "Intel Iris OpenGL Engine";
75
+ return origGetParam.call(this, parameter);
76
+ };
77
+ const cpuCounts = [4, 6, 8, 8];
78
+ Object.defineProperty(navigator, "hardwareConcurrency", {
79
+ get: () => cpuCounts[Math.floor(Math.random() * cpuCounts.length)],
80
+ configurable: true
81
+ });
82
+ Object.defineProperty(navigator, "deviceMemory", {
83
+ get: () => [4, 8][Math.floor(Math.random() * 2)],
84
+ configurable: true
85
+ });
86
+ }).catch(() => {
87
+ });
129
88
  }
130
- export async function extractIframeContent(page) {
131
- const results = [];
132
- try {
133
- const iframeSrcs = await page.evaluate(() => Array.from(document.querySelectorAll('iframe')).map(f => f.getAttribute('src') ?? f.getAttribute('data-src') ?? '')).catch(() => []);
134
- for (const frame of page.frames()) {
135
- const src = frame.url();
136
- if (!src || src === 'about:blank' || src === page.url())
137
- continue;
138
- try {
139
- const content = await frame.content().catch(() => '');
140
- if (!content || content.length < 50)
141
- continue;
142
- // Simple text extraction from iframe HTML
143
- const text = content.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
144
- .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
145
- .replace(/<[^>]+>/g, ' ')
146
- .replace(/\s+/g, ' ')
147
- .trim();
148
- results.push({ src, content: content.slice(0, 10_000), markdown: text.slice(0, 5_000) });
149
- }
150
- catch { /* skip this frame */ }
151
- }
89
+ async function infiniteScrollToBottom(page, opts = {}) {
90
+ const { maxScrolls = 50, scrollDelay = 400, stabilizeMs = 600 } = opts;
91
+ let scrollCount = 0;
92
+ let stableScrollCount = 0;
93
+ let previousHeight = 0;
94
+ try {
95
+ while (scrollCount < maxScrolls) {
96
+ previousHeight = await page.evaluate(() => document.documentElement.scrollHeight).catch(() => 0);
97
+ const scrollBy = 500 + Math.floor(Math.random() * 300);
98
+ await page.evaluate((by) => window.scrollBy(0, by), scrollBy).catch(() => {
99
+ });
100
+ await humanDelay(scrollDelay - 100, scrollDelay + 200);
101
+ await page.waitForTimeout(stabilizeMs).catch(() => {
102
+ });
103
+ scrollCount++;
104
+ const newHeight = await page.evaluate(() => document.documentElement.scrollHeight).catch(() => 0);
105
+ if (newHeight - previousHeight < 10) {
106
+ stableScrollCount++;
107
+ if (stableScrollCount >= 3) break;
108
+ } else {
109
+ stableScrollCount = 0;
110
+ }
111
+ previousHeight = newHeight;
152
112
  }
153
- catch { /* non-fatal */ }
154
- return results;
113
+ } catch {
114
+ }
115
+ return { scrollCount, heightReached: previousHeight };
155
116
  }
156
- export async function pierceShadowDom(page) {
157
- try {
158
- return await page.evaluate(() => {
159
- const parts = [];
160
- function walkShadow(root) {
161
- const shadow = root.shadowRoot;
162
- if (shadow) {
163
- parts.push(shadow.innerHTML ?? '');
164
- for (const child of [...shadow.querySelectorAll('*')])
165
- walkShadow(child);
166
- }
167
- for (const child of Array.from(root.querySelectorAll(':not(script):not(style)'))) {
168
- if (child.shadowRoot)
169
- walkShadow(child);
170
- }
171
- }
172
- walkShadow(document.body);
173
- return parts
174
- .join('\n')
175
- .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
176
- .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
177
- .replace(/<[^>]+>/g, ' ')
178
- .replace(/\s+/g, ' ')
179
- .trim()
180
- .slice(0, 10_000);
181
- }).catch(() => '');
182
- }
183
- catch {
184
- return '';
117
+ async function extractIframeContent(page) {
118
+ const results = [];
119
+ try {
120
+ const iframeSrcs = await page.evaluate(
121
+ () => Array.from(document.querySelectorAll("iframe")).map((f) => f.getAttribute("src") ?? f.getAttribute("data-src") ?? "")
122
+ ).catch(() => []);
123
+ for (const frame of page.frames()) {
124
+ const src = frame.url();
125
+ if (!src || src === "about:blank" || src === page.url()) continue;
126
+ try {
127
+ const content = await frame.content().catch(() => "");
128
+ if (!content || content.length < 50) continue;
129
+ const text = content.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
130
+ results.push({ src, content: content.slice(0, 1e4), markdown: text.slice(0, 5e3) });
131
+ } catch {
132
+ }
185
133
  }
134
+ } catch {
135
+ }
136
+ return results;
137
+ }
138
+ async function pierceShadowDom(page) {
139
+ try {
140
+ return await page.evaluate(() => {
141
+ const parts = [];
142
+ function walkShadow(root) {
143
+ const shadow = root.shadowRoot;
144
+ if (shadow) {
145
+ parts.push(shadow.innerHTML ?? "");
146
+ for (const child of [...shadow.querySelectorAll("*")]) walkShadow(child);
147
+ }
148
+ for (const child of Array.from(root.querySelectorAll(":not(script):not(style)"))) {
149
+ if (child.shadowRoot) walkShadow(child);
150
+ }
151
+ }
152
+ walkShadow(document.body);
153
+ return parts.join("\n").replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 1e4);
154
+ }).catch(() => "");
155
+ } catch {
156
+ return "";
157
+ }
186
158
  }
159
+ export {
160
+ extractIframeContent,
161
+ humanDelay,
162
+ infiniteScrollToBottom,
163
+ injectFingerprintOverrides,
164
+ pierceShadowDom,
165
+ simulateClick,
166
+ simulateMouseHover,
167
+ simulateMouseMovement
168
+ };
169
+ //# sourceMappingURL=human-behavior.js.map
package/dist/telemetry.js CHANGED
@@ -1,91 +1,77 @@
1
- /**
2
- * Lightweight OTel-compatible tracing for Zeta crawler.
3
- *
4
- * Emits spans as structured JSON logs compatible with OpenTelemetry format.
5
- * Drop-in upgrade path: replace this module with @opentelemetry/sdk-node when needed.
6
- *
7
- * Usage:
8
- * const span = startSpan('crawl.url', { url, engine });
9
- * // ... do work ...
10
- * endSpan(span, { screensFound: 3 });
11
- */
12
- import { randomBytes } from 'node:crypto';
13
- // Active trace context — per async execution context (simplified: per-process global)
1
+ import { randomBytes } from "node:crypto";
14
2
  let currentTraceId = null;
15
3
  let currentSpanId = null;
16
4
  function genId(bytes) {
17
- return randomBytes(bytes).toString('hex');
5
+ return randomBytes(bytes).toString("hex");
18
6
  }
19
- /** Start a new span. If a trace is already active, the new span is a child. */
20
- export function startSpan(name, attributes = {}) {
21
- const traceId = currentTraceId ?? genId(16);
22
- const spanId = genId(8);
23
- const parentSpanId = currentSpanId ?? undefined;
24
- if (!currentTraceId)
25
- currentTraceId = traceId;
26
- currentSpanId = spanId;
27
- return { traceId, spanId, parentSpanId, name, startTimeMs: Date.now(), attributes };
7
+ function startSpan(name, attributes = {}) {
8
+ const traceId = currentTraceId ?? genId(16);
9
+ const spanId = genId(8);
10
+ const parentSpanId = currentSpanId ?? void 0;
11
+ if (!currentTraceId) currentTraceId = traceId;
12
+ currentSpanId = spanId;
13
+ return { traceId, spanId, parentSpanId, name, startTimeMs: Date.now(), attributes };
28
14
  }
29
- /** End a span and emit it as a structured log line. */
30
- export function endSpan(span, extraAttributes = {}, error) {
31
- const endTimeMs = Date.now();
32
- const durationMs = endTimeMs - span.startTimeMs;
33
- const finished = {
34
- ...span,
35
- attributes: { ...span.attributes, ...extraAttributes },
36
- endTimeMs,
37
- durationMs,
38
- status: error ? 'error' : 'ok',
39
- error: error?.message,
40
- };
41
- // Reset context when root span ends
42
- if (!span.parentSpanId) {
43
- currentTraceId = null;
44
- currentSpanId = span.parentSpanId ?? null;
45
- }
46
- else {
47
- currentSpanId = span.parentSpanId ?? null;
48
- }
49
- // Emit as structured JSON — compatible with Datadog/Grafana log parsing
50
- console.log(JSON.stringify({
51
- ts: new Date(endTimeMs).toISOString(),
52
- level: error ? 'error' : 'info',
53
- msg: `span:${finished.name}`,
54
- trace_id: finished.traceId,
55
- span_id: finished.spanId,
56
- parent_span_id: finished.parentSpanId,
57
- duration_ms: durationMs,
58
- status: finished.status,
59
- error: finished.error,
60
- ...finished.attributes,
61
- }));
62
- return finished;
15
+ function endSpan(span, extraAttributes = {}, error) {
16
+ const endTimeMs = Date.now();
17
+ const durationMs = endTimeMs - span.startTimeMs;
18
+ const finished = {
19
+ ...span,
20
+ attributes: { ...span.attributes, ...extraAttributes },
21
+ endTimeMs,
22
+ durationMs,
23
+ status: error ? "error" : "ok",
24
+ error: error?.message
25
+ };
26
+ if (!span.parentSpanId) {
27
+ currentTraceId = null;
28
+ currentSpanId = span.parentSpanId ?? null;
29
+ } else {
30
+ currentSpanId = span.parentSpanId ?? null;
31
+ }
32
+ console.log(JSON.stringify({
33
+ ts: new Date(endTimeMs).toISOString(),
34
+ level: error ? "error" : "info",
35
+ msg: `span:${finished.name}`,
36
+ trace_id: finished.traceId,
37
+ span_id: finished.spanId,
38
+ parent_span_id: finished.parentSpanId,
39
+ duration_ms: durationMs,
40
+ status: finished.status,
41
+ error: finished.error,
42
+ ...finished.attributes
43
+ }));
44
+ return finished;
63
45
  }
64
- /** Convenience: wrap an async function in a span. */
65
- export async function withSpan(name, attributes, fn) {
66
- const span = startSpan(name, attributes);
67
- try {
68
- const result = await fn(span);
69
- endSpan(span);
70
- return result;
71
- }
72
- catch (err) {
73
- endSpan(span, {}, err);
74
- throw err;
75
- }
46
+ async function withSpan(name, attributes, fn) {
47
+ const span = startSpan(name, attributes);
48
+ try {
49
+ const result = await fn(span);
50
+ endSpan(span);
51
+ return result;
52
+ } catch (err) {
53
+ endSpan(span, {}, err);
54
+ throw err;
55
+ }
76
56
  }
77
- /** Counter metrics — increment and log on flush. */
78
- const counters = new Map();
79
- export function increment(metric, value = 1, tags = {}) {
80
- const key = metric + (Object.keys(tags).length ? ':' + Object.entries(tags).map(([k, v]) => `${k}=${v}`).join(',') : '');
81
- counters.set(key, (counters.get(key) ?? 0) + value);
57
+ const counters = /* @__PURE__ */ new Map();
58
+ function increment(metric, value = 1, tags = {}) {
59
+ const key = metric + (Object.keys(tags).length ? ":" + Object.entries(tags).map(([k, v]) => `${k}=${v}`).join(",") : "");
60
+ counters.set(key, (counters.get(key) ?? 0) + value);
82
61
  }
83
- /** Flush counters as a structured log — call periodically (e.g. at job end). */
84
- export function flushMetrics() {
85
- const snapshot = Object.fromEntries(counters);
86
- if (Object.keys(snapshot).length > 0) {
87
- console.log(JSON.stringify({ ts: new Date().toISOString(), level: 'info', msg: 'metrics:flush', ...snapshot }));
88
- counters.clear();
89
- }
90
- return snapshot;
62
+ function flushMetrics() {
63
+ const snapshot = Object.fromEntries(counters);
64
+ if (Object.keys(snapshot).length > 0) {
65
+ console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "info", msg: "metrics:flush", ...snapshot }));
66
+ counters.clear();
67
+ }
68
+ return snapshot;
91
69
  }
70
+ export {
71
+ endSpan,
72
+ flushMetrics,
73
+ increment,
74
+ startSpan,
75
+ withSpan
76
+ };
77
+ //# sourceMappingURL=telemetry.js.map