lopata 0.5.1 → 0.6.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/dist/dashboard/{chunk-a68x1m5f.css → chunk-3q3dhs4j.css} +8 -0
- package/dist/dashboard/{chunk-rae638a4.js → chunk-4y88h3dc.js} +44 -16
- package/dist/dashboard/index.html +1 -1
- package/package.json +1 -1
- package/src/plugin.ts +7 -98
- package/src/setup-globals.ts +129 -0
- package/src/tracing/global.d.ts +50 -0
- package/src/vite-plugin/globals-plugin.ts +5 -79
|
@@ -683,6 +683,10 @@
|
|
|
683
683
|
height: calc(var(--spacing) * 2.5);
|
|
684
684
|
}
|
|
685
685
|
|
|
686
|
+
.h-4 {
|
|
687
|
+
height: calc(var(--spacing) * 4);
|
|
688
|
+
}
|
|
689
|
+
|
|
686
690
|
.h-5 {
|
|
687
691
|
height: calc(var(--spacing) * 5);
|
|
688
692
|
}
|
|
@@ -880,6 +884,10 @@
|
|
|
880
884
|
min-width: calc(var(--spacing) * 0);
|
|
881
885
|
}
|
|
882
886
|
|
|
887
|
+
.min-w-\[16px\] {
|
|
888
|
+
min-width: 16px;
|
|
889
|
+
}
|
|
890
|
+
|
|
883
891
|
.min-w-\[20px\] {
|
|
884
892
|
min-width: 20px;
|
|
885
893
|
}
|
|
@@ -5044,28 +5044,38 @@ function TraceWaterfall({ spans, events, highlightSpanId, onAddAttributeFilter }
|
|
|
5044
5044
|
childMap.set(key, []);
|
|
5045
5045
|
childMap.get(key).push(s3);
|
|
5046
5046
|
}
|
|
5047
|
+
const processedSpanIdsRef = A2(new Set);
|
|
5047
5048
|
const spanIdKey = spans.map((s3) => s3.spanId).join(",");
|
|
5048
5049
|
y2(() => {
|
|
5049
5050
|
if (spans.length === 0)
|
|
5050
5051
|
return;
|
|
5051
|
-
const
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5052
|
+
const previousProcessed = processedSpanIdsRef.current;
|
|
5053
|
+
const currentIds = spans.map((s3) => s3.spanId);
|
|
5054
|
+
const newSpanIds = new Set(currentIds.filter((id) => !previousProcessed.has(id)));
|
|
5055
|
+
processedSpanIdsRef.current = new Set(currentIds);
|
|
5056
|
+
if (newSpanIds.size === 0)
|
|
5057
|
+
return;
|
|
5058
|
+
setCollapsedSpans((prev) => {
|
|
5059
|
+
const next = new Set(prev);
|
|
5060
|
+
function walk(parentId, depth) {
|
|
5061
|
+
const children = childMap.get(parentId) ?? [];
|
|
5062
|
+
for (const child of children) {
|
|
5063
|
+
if (newSpanIds.has(child.spanId)) {
|
|
5064
|
+
const hasChildren = (childMap.get(child.spanId) ?? []).length > 0;
|
|
5065
|
+
if (hasChildren) {
|
|
5066
|
+
const spanDur = child.durationMs ?? 0;
|
|
5067
|
+
const significantDuration = spanDur > traceDuration * 0.1;
|
|
5068
|
+
if (depth >= 2 && !significantDuration) {
|
|
5069
|
+
next.add(child.spanId);
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
5061
5072
|
}
|
|
5073
|
+
walk(child.spanId, depth + 1);
|
|
5062
5074
|
}
|
|
5063
|
-
walk(child.spanId, depth + 1);
|
|
5064
5075
|
}
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
setExpandedSpan(null);
|
|
5076
|
+
walk(null, 0);
|
|
5077
|
+
return next;
|
|
5078
|
+
});
|
|
5069
5079
|
}, [spanIdKey]);
|
|
5070
5080
|
const toggleCollapse = (spanId) => {
|
|
5071
5081
|
setCollapsedSpans((prev) => {
|
|
@@ -5090,6 +5100,19 @@ function TraceWaterfall({ spans, events, highlightSpanId, onAddAttributeFilter }
|
|
|
5090
5100
|
return result;
|
|
5091
5101
|
}
|
|
5092
5102
|
const flatSpans = flattenTree(null, 0);
|
|
5103
|
+
const descendantErrorCount = new Map;
|
|
5104
|
+
function countErrors(parentId) {
|
|
5105
|
+
let count = 0;
|
|
5106
|
+
for (const child of childMap.get(parentId) ?? []) {
|
|
5107
|
+
if (child.status === "error")
|
|
5108
|
+
count++;
|
|
5109
|
+
const childErrors = countErrors(child.spanId);
|
|
5110
|
+
count += childErrors;
|
|
5111
|
+
descendantErrorCount.set(child.spanId, childErrors);
|
|
5112
|
+
}
|
|
5113
|
+
return count;
|
|
5114
|
+
}
|
|
5115
|
+
countErrors(null);
|
|
5093
5116
|
const getParentAttributes = (span) => {
|
|
5094
5117
|
if (!span.parentSpanId)
|
|
5095
5118
|
return {};
|
|
@@ -5146,8 +5169,13 @@ function TraceWaterfall({ spans, events, highlightSpanId, onAddAttributeFilter }
|
|
|
5146
5169
|
class: "inline-block w-4 flex-shrink-0"
|
|
5147
5170
|
}, undefined, false, undefined, this),
|
|
5148
5171
|
/* @__PURE__ */ u3("span", {
|
|
5149
|
-
class:
|
|
5172
|
+
class: `truncate ${span.status === "error" ? "text-red-400" : ""}`,
|
|
5150
5173
|
children: span.name
|
|
5174
|
+
}, undefined, false, undefined, this),
|
|
5175
|
+
(descendantErrorCount.get(span.spanId) ?? 0) > 0 && /* @__PURE__ */ u3("span", {
|
|
5176
|
+
class: "ml-1 flex-shrink-0 inline-flex items-center justify-center min-w-[16px] h-4 px-1 rounded-full text-[10px] font-medium",
|
|
5177
|
+
style: { background: "var(--color-badge-red-bg)", color: "var(--color-badge-red-text)" },
|
|
5178
|
+
children: descendantErrorCount.get(span.spanId)
|
|
5151
5179
|
}, undefined, false, undefined, this)
|
|
5152
5180
|
]
|
|
5153
5181
|
}, undefined, true, undefined, this),
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
10
10
|
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/__dashboard/assets/chunk-
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/__dashboard/assets/chunk-3q3dhs4j.css"><script type="module" crossorigin src="/__dashboard/assets/chunk-4y88h3dc.js"></script></head>
|
|
12
12
|
<body class="h-full bg-surface text-ink" style="font-family: system-ui, -apple-system, sans-serif;">
|
|
13
13
|
<script>
|
|
14
14
|
// Apply saved theme before first paint to prevent flash
|
package/package.json
CHANGED
package/src/plugin.ts
CHANGED
|
@@ -1,102 +1,18 @@
|
|
|
1
1
|
import { plugin } from 'bun'
|
|
2
2
|
import type { BrowserBinding } from './bindings/browser'
|
|
3
|
-
import { SqliteCacheStorage } from './bindings/cache'
|
|
4
|
-
import { FixedLengthStream, IdentityTransformStream } from './bindings/cf-streams'
|
|
5
3
|
import { ContainerBase, getContainer, getRandom } from './bindings/container'
|
|
6
|
-
import { patchGlobalCrypto } from './bindings/crypto-extras'
|
|
7
4
|
import { DurableObjectBase, WebSocketRequestResponsePair } from './bindings/durable-object'
|
|
8
5
|
import { EmailMessage } from './bindings/email'
|
|
9
|
-
import { HTMLRewriter } from './bindings/html-rewriter'
|
|
10
6
|
import type { ImageTransformOptions, OutputOptions } from './bindings/images'
|
|
11
7
|
import { WebSocketPair } from './bindings/websocket-pair'
|
|
12
8
|
import { NonRetryableError, WorkflowEntrypointBase } from './bindings/workflow'
|
|
13
|
-
import { getDatabase } from './db'
|
|
14
9
|
import { globalEnv } from './env'
|
|
15
10
|
import { getActiveExecutionContext } from './execution-context'
|
|
11
|
+
import { setupCloudflareGlobals } from './setup-globals'
|
|
16
12
|
import { getActiveContext } from './tracing/context'
|
|
17
|
-
import {
|
|
18
|
-
import { addSpanEvent, setSpanAttribute, startSpan } from './tracing/span'
|
|
19
|
-
|
|
20
|
-
// Register global `caches` object (CacheStorage) with tracing
|
|
21
|
-
const rawCacheStorage = new SqliteCacheStorage(getDatabase())
|
|
22
|
-
const cacheMethods = ['match', 'put', 'delete']
|
|
23
|
-
|
|
24
|
-
// Instrument the default cache
|
|
25
|
-
rawCacheStorage.default = instrumentBinding(rawCacheStorage.default, {
|
|
26
|
-
type: 'cache',
|
|
27
|
-
name: 'default',
|
|
28
|
-
methods: cacheMethods,
|
|
29
|
-
}) as typeof rawCacheStorage.default
|
|
30
|
-
|
|
31
|
-
// Wrap open() to return instrumented caches
|
|
32
|
-
const originalOpen = rawCacheStorage.open.bind(rawCacheStorage)
|
|
33
|
-
rawCacheStorage.open = async (cacheName: string) => {
|
|
34
|
-
const cache = await originalOpen(cacheName)
|
|
35
|
-
return instrumentBinding(cache, {
|
|
36
|
-
type: 'cache',
|
|
37
|
-
name: cacheName,
|
|
38
|
-
methods: cacheMethods,
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
Object.defineProperty(globalThis, 'caches', {
|
|
43
|
-
value: rawCacheStorage,
|
|
44
|
-
writable: false,
|
|
45
|
-
configurable: true,
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
// Register global `HTMLRewriter` class
|
|
49
|
-
Object.defineProperty(globalThis, 'HTMLRewriter', {
|
|
50
|
-
value: HTMLRewriter,
|
|
51
|
-
writable: false,
|
|
52
|
-
configurable: true,
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
// Register global `WebSocketPair` class
|
|
56
|
-
Object.defineProperty(globalThis, 'WebSocketPair', {
|
|
57
|
-
value: WebSocketPair,
|
|
58
|
-
writable: false,
|
|
59
|
-
configurable: true,
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
// Register global CF stream classes
|
|
63
|
-
Object.defineProperty(globalThis, 'IdentityTransformStream', {
|
|
64
|
-
value: IdentityTransformStream,
|
|
65
|
-
writable: false,
|
|
66
|
-
configurable: true,
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
Object.defineProperty(globalThis, 'FixedLengthStream', {
|
|
70
|
-
value: FixedLengthStream,
|
|
71
|
-
writable: false,
|
|
72
|
-
configurable: true,
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
// Patch crypto with CF-specific extensions (timingSafeEqual, DigestStream)
|
|
76
|
-
patchGlobalCrypto()
|
|
77
|
-
|
|
78
|
-
// Set navigator.userAgent to match Cloudflare Workers
|
|
79
|
-
Object.defineProperty(globalThis.navigator, 'userAgent', {
|
|
80
|
-
value: 'Cloudflare-Workers',
|
|
81
|
-
writable: false,
|
|
82
|
-
configurable: true,
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
// Set navigator.language (behind enable_navigator_language compat flag in CF)
|
|
86
|
-
if (!globalThis.navigator.language) {
|
|
87
|
-
Object.defineProperty(globalThis.navigator, 'language', {
|
|
88
|
-
value: 'en',
|
|
89
|
-
writable: false,
|
|
90
|
-
configurable: true,
|
|
91
|
-
})
|
|
92
|
-
}
|
|
13
|
+
import { addSpanEvent, persistError, setSpanAttribute, startSpan } from './tracing/span'
|
|
93
14
|
|
|
94
|
-
|
|
95
|
-
Object.defineProperty(globalThis.performance, 'timeOrigin', {
|
|
96
|
-
value: 0,
|
|
97
|
-
writable: false,
|
|
98
|
-
configurable: true,
|
|
99
|
-
})
|
|
15
|
+
setupCloudflareGlobals()
|
|
100
16
|
|
|
101
17
|
// Register addEventListener shim for legacy service worker syntax
|
|
102
18
|
// Workers that use addEventListener("fetch", handler) instead of export default { fetch }
|
|
@@ -113,17 +29,6 @@ Object.defineProperty(globalThis, 'addEventListener', {
|
|
|
113
29
|
}) /** @internal Get the registered service worker fetch handler */
|
|
114
30
|
;(globalThis as any).__lopata_sw_handlers = _serviceWorkerHandlers
|
|
115
31
|
|
|
116
|
-
// Register scheduler.wait(ms) — await-able setTimeout alternative
|
|
117
|
-
Object.defineProperty(globalThis, 'scheduler', {
|
|
118
|
-
value: {
|
|
119
|
-
wait(ms: number): Promise<void> {
|
|
120
|
-
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
writable: false,
|
|
124
|
-
configurable: true,
|
|
125
|
-
})
|
|
126
|
-
|
|
127
32
|
// ─── Console instrumentation ─────────────────────────────────────────
|
|
128
33
|
// Captures console.log/info/warn/error/debug as span events when inside a trace context.
|
|
129
34
|
|
|
@@ -151,6 +56,10 @@ for (const method of consoleMethods) {
|
|
|
151
56
|
if (!ctx) return
|
|
152
57
|
const message = args.map(formatConsoleArg).join(' ')
|
|
153
58
|
addSpanEvent(`console.${method}`, method, message)
|
|
59
|
+
if (method === 'error') {
|
|
60
|
+
const errorArg = args.find((a) => a instanceof Error)
|
|
61
|
+
persistError(errorArg ?? new Error(message), 'console.error')
|
|
62
|
+
}
|
|
154
63
|
}
|
|
155
64
|
}
|
|
156
65
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { SqliteCacheStorage } from './bindings/cache'
|
|
2
|
+
import { FixedLengthStream, IdentityTransformStream } from './bindings/cf-streams'
|
|
3
|
+
import { patchGlobalCrypto } from './bindings/crypto-extras'
|
|
4
|
+
import { HTMLRewriter } from './bindings/html-rewriter'
|
|
5
|
+
import { WebSocketPair } from './bindings/websocket-pair'
|
|
6
|
+
import { getDatabase } from './db'
|
|
7
|
+
import { instrumentBinding } from './tracing/instrument'
|
|
8
|
+
import { addSpanEvent, setSpanAttribute, startSpan } from './tracing/span'
|
|
9
|
+
|
|
10
|
+
let initialized = false
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sets up global Cloudflare-compatible APIs:
|
|
14
|
+
* caches, HTMLRewriter, WebSocketPair, IdentityTransformStream, FixedLengthStream,
|
|
15
|
+
* navigator.userAgent, navigator.language, performance.timeOrigin, scheduler.wait(),
|
|
16
|
+
* crypto extensions, and __lopata userland tracing API.
|
|
17
|
+
*
|
|
18
|
+
* Idempotent — safe to call multiple times.
|
|
19
|
+
*/
|
|
20
|
+
export function setupCloudflareGlobals() {
|
|
21
|
+
if (initialized) return
|
|
22
|
+
initialized = true // ─── Userland tracing API ────────────────────────────────────────────
|
|
23
|
+
// Exposes a lightweight global that user code can call to create custom
|
|
24
|
+
// spans visible in the Lopata dashboard. In production (without Lopata)
|
|
25
|
+
// the global is simply absent, so the user's thin wrapper becomes a no-op.
|
|
26
|
+
;(globalThis as any).__lopata = {
|
|
27
|
+
trace<T>(name: string, attrsOrFn: Record<string, unknown> | (() => T | Promise<T>), maybeFn?: () => T | Promise<T>): Promise<T> {
|
|
28
|
+
const fn = typeof attrsOrFn === 'function' ? attrsOrFn : maybeFn!
|
|
29
|
+
const attributes = typeof attrsOrFn === 'function' ? undefined : attrsOrFn
|
|
30
|
+
return startSpan({ name, attributes }, fn)
|
|
31
|
+
},
|
|
32
|
+
setAttribute: setSpanAttribute,
|
|
33
|
+
addEvent(name: string, message?: string, attrs?: Record<string, unknown>): void {
|
|
34
|
+
addSpanEvent(name, 'info', message ?? '', attrs)
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Register global `caches` object (CacheStorage) with tracing
|
|
39
|
+
const rawCacheStorage = new SqliteCacheStorage(getDatabase())
|
|
40
|
+
const cacheMethods = ['match', 'put', 'delete']
|
|
41
|
+
|
|
42
|
+
// Instrument the default cache
|
|
43
|
+
rawCacheStorage.default = instrumentBinding(rawCacheStorage.default, {
|
|
44
|
+
type: 'cache',
|
|
45
|
+
name: 'default',
|
|
46
|
+
methods: cacheMethods,
|
|
47
|
+
}) as typeof rawCacheStorage.default
|
|
48
|
+
|
|
49
|
+
// Wrap open() to return instrumented caches
|
|
50
|
+
const originalOpen = rawCacheStorage.open.bind(rawCacheStorage)
|
|
51
|
+
rawCacheStorage.open = async (cacheName: string) => {
|
|
52
|
+
const cache = await originalOpen(cacheName)
|
|
53
|
+
return instrumentBinding(cache, {
|
|
54
|
+
type: 'cache',
|
|
55
|
+
name: cacheName,
|
|
56
|
+
methods: cacheMethods,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
Object.defineProperty(globalThis, 'caches', {
|
|
61
|
+
value: rawCacheStorage,
|
|
62
|
+
writable: false,
|
|
63
|
+
configurable: true,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// Register global `HTMLRewriter` class
|
|
67
|
+
Object.defineProperty(globalThis, 'HTMLRewriter', {
|
|
68
|
+
value: HTMLRewriter,
|
|
69
|
+
writable: false,
|
|
70
|
+
configurable: true,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Register global `WebSocketPair` class
|
|
74
|
+
Object.defineProperty(globalThis, 'WebSocketPair', {
|
|
75
|
+
value: WebSocketPair,
|
|
76
|
+
writable: false,
|
|
77
|
+
configurable: true,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Register global CF stream classes
|
|
81
|
+
Object.defineProperty(globalThis, 'IdentityTransformStream', {
|
|
82
|
+
value: IdentityTransformStream,
|
|
83
|
+
writable: false,
|
|
84
|
+
configurable: true,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
Object.defineProperty(globalThis, 'FixedLengthStream', {
|
|
88
|
+
value: FixedLengthStream,
|
|
89
|
+
writable: false,
|
|
90
|
+
configurable: true,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Patch crypto with CF-specific extensions (timingSafeEqual, DigestStream)
|
|
94
|
+
patchGlobalCrypto()
|
|
95
|
+
|
|
96
|
+
// Set navigator.userAgent to match Cloudflare Workers
|
|
97
|
+
Object.defineProperty(globalThis.navigator, 'userAgent', {
|
|
98
|
+
value: 'Cloudflare-Workers',
|
|
99
|
+
writable: false,
|
|
100
|
+
configurable: true,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Set navigator.language (behind enable_navigator_language compat flag in CF)
|
|
104
|
+
if (!globalThis.navigator.language) {
|
|
105
|
+
Object.defineProperty(globalThis.navigator, 'language', {
|
|
106
|
+
value: 'en',
|
|
107
|
+
writable: false,
|
|
108
|
+
configurable: true,
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Set performance.timeOrigin to 0 (CF semantics)
|
|
113
|
+
Object.defineProperty(globalThis.performance, 'timeOrigin', {
|
|
114
|
+
value: 0,
|
|
115
|
+
writable: false,
|
|
116
|
+
configurable: true,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Register scheduler.wait(ms) — await-able setTimeout alternative
|
|
120
|
+
Object.defineProperty(globalThis, 'scheduler', {
|
|
121
|
+
value: {
|
|
122
|
+
wait(ms: number): Promise<void> {
|
|
123
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
writable: false,
|
|
127
|
+
configurable: true,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lopata userland tracing API.
|
|
3
|
+
*
|
|
4
|
+
* Available on `globalThis.__lopata` when running under Lopata dev server.
|
|
5
|
+
* In production (Cloudflare Workers) the global is `undefined` — wrap calls
|
|
6
|
+
* in a thin helper that falls back to a no-op:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // app/lib/trace.ts
|
|
10
|
+
* type TraceFn = <T>(name: string, fn: () => T | Promise<T>) => Promise<T>
|
|
11
|
+
*
|
|
12
|
+
* export const trace: TraceFn = (name, fn) =>
|
|
13
|
+
* globalThis.__lopata?.trace(name, fn) ?? fn()
|
|
14
|
+
*
|
|
15
|
+
* export const setAttribute = (key: string, value: unknown) =>
|
|
16
|
+
* globalThis.__lopata?.setAttribute(key, value)
|
|
17
|
+
*
|
|
18
|
+
* export const addEvent = (name: string, message?: string) =>
|
|
19
|
+
* globalThis.__lopata?.addEvent(name, message)
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Add this file to your tsconfig types to get autocomplete:
|
|
23
|
+
* ```json
|
|
24
|
+
* { "compilerOptions": { "types": ["lopata/src/tracing/global"] } }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
interface LopataTracing {
|
|
29
|
+
/**
|
|
30
|
+
* Create a traced span around `fn`. The span is visible in the Lopata
|
|
31
|
+
* dashboard and becomes a child of the currently active span (if any).
|
|
32
|
+
*/
|
|
33
|
+
trace<T>(name: string, fn: () => T | Promise<T>): Promise<T>
|
|
34
|
+
/**
|
|
35
|
+
* Create a traced span with custom attributes.
|
|
36
|
+
*/
|
|
37
|
+
trace<T>(name: string, attrs: Record<string, unknown>, fn: () => T | Promise<T>): Promise<T>
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set an attribute on the currently active span.
|
|
41
|
+
*/
|
|
42
|
+
setAttribute(key: string, value: unknown): void
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Add an event (log entry) to the currently active span.
|
|
46
|
+
*/
|
|
47
|
+
addEvent(name: string, message?: string, attrs?: Record<string, unknown>): void
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
declare var __lopata: LopataTracing | undefined
|
|
@@ -3,13 +3,11 @@ import type { Plugin } from 'vite'
|
|
|
3
3
|
let initialized = false
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Sets up global Cloudflare-compatible APIs in the Bun process
|
|
7
|
-
* caches, HTMLRewriter, WebSocketPair, IdentityTransformStream, FixedLengthStream,
|
|
8
|
-
* navigator.userAgent, scheduler.wait(), crypto extensions.
|
|
6
|
+
* Sets up global Cloudflare-compatible APIs in the Bun process.
|
|
9
7
|
*
|
|
10
8
|
* Runs once on configureServer (before middleware), idempotent.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Uses dynamic import because the plugin file is externalized by Vite's config bundler —
|
|
10
|
+
* the import runs at dev server startup time through Bun's native loader.
|
|
13
11
|
*/
|
|
14
12
|
export function globalsPlugin(): Plugin {
|
|
15
13
|
return {
|
|
@@ -19,80 +17,8 @@ export function globalsPlugin(): Plugin {
|
|
|
19
17
|
if (initialized) return
|
|
20
18
|
initialized = true
|
|
21
19
|
|
|
22
|
-
const {
|
|
23
|
-
|
|
24
|
-
const { WebSocketPair } = await import('../bindings/websocket-pair.ts')
|
|
25
|
-
const { IdentityTransformStream, FixedLengthStream } = await import('../bindings/cf-streams.ts')
|
|
26
|
-
const { patchGlobalCrypto } = await import('../bindings/crypto-extras.ts')
|
|
27
|
-
const { getDatabase } = await import('../db.ts')
|
|
28
|
-
const { instrumentBinding } = await import('../tracing/instrument.ts')
|
|
29
|
-
|
|
30
|
-
// Global caches (CacheStorage)
|
|
31
|
-
const cacheMethods = ['match', 'put', 'delete']
|
|
32
|
-
const rawCacheStorage = new SqliteCacheStorage(getDatabase())
|
|
33
|
-
rawCacheStorage.default = instrumentBinding(rawCacheStorage.default, {
|
|
34
|
-
type: 'cache',
|
|
35
|
-
name: 'default',
|
|
36
|
-
methods: cacheMethods,
|
|
37
|
-
}) as typeof rawCacheStorage.default
|
|
38
|
-
|
|
39
|
-
const originalOpen = rawCacheStorage.open.bind(rawCacheStorage)
|
|
40
|
-
rawCacheStorage.open = async (cacheName: string) => {
|
|
41
|
-
const cache = await originalOpen(cacheName)
|
|
42
|
-
return instrumentBinding(cache, {
|
|
43
|
-
type: 'cache',
|
|
44
|
-
name: cacheName,
|
|
45
|
-
methods: cacheMethods,
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
Object.defineProperty(globalThis, 'caches', {
|
|
50
|
-
value: rawCacheStorage,
|
|
51
|
-
writable: false,
|
|
52
|
-
configurable: true,
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
Object.defineProperty(globalThis, 'HTMLRewriter', {
|
|
56
|
-
value: HTMLRewriter,
|
|
57
|
-
writable: false,
|
|
58
|
-
configurable: true,
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
Object.defineProperty(globalThis, 'WebSocketPair', {
|
|
62
|
-
value: WebSocketPair,
|
|
63
|
-
writable: false,
|
|
64
|
-
configurable: true,
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
Object.defineProperty(globalThis, 'IdentityTransformStream', {
|
|
68
|
-
value: IdentityTransformStream,
|
|
69
|
-
writable: false,
|
|
70
|
-
configurable: true,
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
Object.defineProperty(globalThis, 'FixedLengthStream', {
|
|
74
|
-
value: FixedLengthStream,
|
|
75
|
-
writable: false,
|
|
76
|
-
configurable: true,
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
patchGlobalCrypto()
|
|
80
|
-
|
|
81
|
-
Object.defineProperty(globalThis.navigator, 'userAgent', {
|
|
82
|
-
value: 'Cloudflare-Workers',
|
|
83
|
-
writable: false,
|
|
84
|
-
configurable: true,
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
Object.defineProperty(globalThis, 'scheduler', {
|
|
88
|
-
value: {
|
|
89
|
-
wait(ms: number): Promise<void> {
|
|
90
|
-
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
writable: false,
|
|
94
|
-
configurable: true,
|
|
95
|
-
})
|
|
20
|
+
const { setupCloudflareGlobals } = await import('../setup-globals.ts')
|
|
21
|
+
setupCloudflareGlobals()
|
|
96
22
|
},
|
|
97
23
|
}
|
|
98
24
|
}
|