lopata 0.5.2 → 0.7.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-rae638a4.js → chunk-5nxa3jfc.js} +296 -93
- package/dist/dashboard/{chunk-a68x1m5f.css → chunk-pqnphvm2.css} +16 -0
- package/dist/dashboard/index.html +1 -1
- package/package.json +1 -1
- package/src/bindings/do-websocket-bridge.ts +9 -0
- package/src/bindings/durable-object.ts +6 -0
- package/src/bindings/websocket-pair.ts +13 -0
- package/src/cli/dev.ts +3 -0
- package/src/plugin.ts +3 -112
- package/src/setup-globals.ts +150 -0
- package/src/vite-plugin/config-plugin.ts +6 -2
- package/src/vite-plugin/dev-server-plugin.ts +217 -196
- package/src/vite-plugin/globals-plugin.ts +5 -79
package/src/plugin.ts
CHANGED
|
@@ -1,116 +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, persistError, setSpanAttribute, startSpan } from './tracing/span' // ─── Userland tracing API ────────────────────────────────────────────
|
|
19
|
-
// Exposes a lightweight global that user code can call to create custom
|
|
20
|
-
// spans visible in the Lopata dashboard. In production (without Lopata)
|
|
21
|
-
// the global is simply absent, so the user's thin wrapper becomes a no-op.
|
|
22
|
-
;(globalThis as any).__lopata = {
|
|
23
|
-
trace<T>(name: string, attrsOrFn: Record<string, unknown> | (() => T | Promise<T>), maybeFn?: () => T | Promise<T>): Promise<T> {
|
|
24
|
-
const fn = typeof attrsOrFn === 'function' ? attrsOrFn : maybeFn!
|
|
25
|
-
const attributes = typeof attrsOrFn === 'function' ? undefined : attrsOrFn
|
|
26
|
-
return startSpan({ name, attributes }, fn)
|
|
27
|
-
},
|
|
28
|
-
setAttribute: setSpanAttribute,
|
|
29
|
-
addEvent(name: string, message?: string, attrs?: Record<string, unknown>): void {
|
|
30
|
-
addSpanEvent(name, 'info', message ?? '', attrs)
|
|
31
|
-
},
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Register global `caches` object (CacheStorage) with tracing
|
|
35
|
-
const rawCacheStorage = new SqliteCacheStorage(getDatabase())
|
|
36
|
-
const cacheMethods = ['match', 'put', 'delete']
|
|
37
|
-
|
|
38
|
-
// Instrument the default cache
|
|
39
|
-
rawCacheStorage.default = instrumentBinding(rawCacheStorage.default, {
|
|
40
|
-
type: 'cache',
|
|
41
|
-
name: 'default',
|
|
42
|
-
methods: cacheMethods,
|
|
43
|
-
}) as typeof rawCacheStorage.default
|
|
44
|
-
|
|
45
|
-
// Wrap open() to return instrumented caches
|
|
46
|
-
const originalOpen = rawCacheStorage.open.bind(rawCacheStorage)
|
|
47
|
-
rawCacheStorage.open = async (cacheName: string) => {
|
|
48
|
-
const cache = await originalOpen(cacheName)
|
|
49
|
-
return instrumentBinding(cache, {
|
|
50
|
-
type: 'cache',
|
|
51
|
-
name: cacheName,
|
|
52
|
-
methods: cacheMethods,
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
Object.defineProperty(globalThis, 'caches', {
|
|
57
|
-
value: rawCacheStorage,
|
|
58
|
-
writable: false,
|
|
59
|
-
configurable: true,
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
// Register global `HTMLRewriter` class
|
|
63
|
-
Object.defineProperty(globalThis, 'HTMLRewriter', {
|
|
64
|
-
value: HTMLRewriter,
|
|
65
|
-
writable: false,
|
|
66
|
-
configurable: true,
|
|
67
|
-
})
|
|
13
|
+
import { addSpanEvent, persistError, setSpanAttribute, startSpan } from './tracing/span'
|
|
68
14
|
|
|
69
|
-
|
|
70
|
-
Object.defineProperty(globalThis, 'WebSocketPair', {
|
|
71
|
-
value: WebSocketPair,
|
|
72
|
-
writable: false,
|
|
73
|
-
configurable: true,
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
// Register global CF stream classes
|
|
77
|
-
Object.defineProperty(globalThis, 'IdentityTransformStream', {
|
|
78
|
-
value: IdentityTransformStream,
|
|
79
|
-
writable: false,
|
|
80
|
-
configurable: true,
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
Object.defineProperty(globalThis, 'FixedLengthStream', {
|
|
84
|
-
value: FixedLengthStream,
|
|
85
|
-
writable: false,
|
|
86
|
-
configurable: true,
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
// Patch crypto with CF-specific extensions (timingSafeEqual, DigestStream)
|
|
90
|
-
patchGlobalCrypto()
|
|
91
|
-
|
|
92
|
-
// Set navigator.userAgent to match Cloudflare Workers
|
|
93
|
-
Object.defineProperty(globalThis.navigator, 'userAgent', {
|
|
94
|
-
value: 'Cloudflare-Workers',
|
|
95
|
-
writable: false,
|
|
96
|
-
configurable: true,
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
// Set navigator.language (behind enable_navigator_language compat flag in CF)
|
|
100
|
-
if (!globalThis.navigator.language) {
|
|
101
|
-
Object.defineProperty(globalThis.navigator, 'language', {
|
|
102
|
-
value: 'en',
|
|
103
|
-
writable: false,
|
|
104
|
-
configurable: true,
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Set performance.timeOrigin to 0 (CF semantics)
|
|
109
|
-
Object.defineProperty(globalThis.performance, 'timeOrigin', {
|
|
110
|
-
value: 0,
|
|
111
|
-
writable: false,
|
|
112
|
-
configurable: true,
|
|
113
|
-
})
|
|
15
|
+
setupCloudflareGlobals()
|
|
114
16
|
|
|
115
17
|
// Register addEventListener shim for legacy service worker syntax
|
|
116
18
|
// Workers that use addEventListener("fetch", handler) instead of export default { fetch }
|
|
@@ -127,17 +29,6 @@ Object.defineProperty(globalThis, 'addEventListener', {
|
|
|
127
29
|
}) /** @internal Get the registered service worker fetch handler */
|
|
128
30
|
;(globalThis as any).__lopata_sw_handlers = _serviceWorkerHandlers
|
|
129
31
|
|
|
130
|
-
// Register scheduler.wait(ms) — await-able setTimeout alternative
|
|
131
|
-
Object.defineProperty(globalThis, 'scheduler', {
|
|
132
|
-
value: {
|
|
133
|
-
wait(ms: number): Promise<void> {
|
|
134
|
-
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
writable: false,
|
|
138
|
-
configurable: true,
|
|
139
|
-
})
|
|
140
|
-
|
|
141
32
|
// ─── Console instrumentation ─────────────────────────────────────────
|
|
142
33
|
// Captures console.log/info/warn/error/debug as span events when inside a trace context.
|
|
143
34
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { SqliteCacheStorage } from './bindings/cache'
|
|
2
|
+
import { FixedLengthStream, IdentityTransformStream } from './bindings/cf-streams'
|
|
3
|
+
import { patchGlobalCrypto } from './bindings/crypto-extras'
|
|
4
|
+
import { WebSocketRequestResponsePair } from './bindings/durable-object'
|
|
5
|
+
import { HTMLRewriter } from './bindings/html-rewriter'
|
|
6
|
+
import { WebSocketPair } from './bindings/websocket-pair'
|
|
7
|
+
import { getDatabase } from './db'
|
|
8
|
+
import { instrumentBinding } from './tracing/instrument'
|
|
9
|
+
import { addSpanEvent, setSpanAttribute, startSpan } from './tracing/span'
|
|
10
|
+
|
|
11
|
+
let initialized = false
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Sets up global Cloudflare-compatible APIs:
|
|
15
|
+
* caches, HTMLRewriter, WebSocketPair, IdentityTransformStream, FixedLengthStream,
|
|
16
|
+
* navigator.userAgent, navigator.language, performance.timeOrigin, scheduler.wait(),
|
|
17
|
+
* crypto extensions, and __lopata userland tracing API.
|
|
18
|
+
*
|
|
19
|
+
* Idempotent — safe to call multiple times.
|
|
20
|
+
*/
|
|
21
|
+
export function setupCloudflareGlobals() {
|
|
22
|
+
if (initialized) return
|
|
23
|
+
initialized = true // ─── Userland tracing API ────────────────────────────────────────────
|
|
24
|
+
// Exposes a lightweight global that user code can call to create custom
|
|
25
|
+
// spans visible in the Lopata dashboard. In production (without Lopata)
|
|
26
|
+
// the global is simply absent, so the user's thin wrapper becomes a no-op.
|
|
27
|
+
;(globalThis as any).__lopata = {
|
|
28
|
+
trace<T>(name: string, attrsOrFn: Record<string, unknown> | (() => T | Promise<T>), maybeFn?: () => T | Promise<T>): Promise<T> {
|
|
29
|
+
const fn = typeof attrsOrFn === 'function' ? attrsOrFn : maybeFn!
|
|
30
|
+
const attributes = typeof attrsOrFn === 'function' ? undefined : attrsOrFn
|
|
31
|
+
return startSpan({ name, attributes }, fn)
|
|
32
|
+
},
|
|
33
|
+
setAttribute: setSpanAttribute,
|
|
34
|
+
addEvent(name: string, message?: string, attrs?: Record<string, unknown>): void {
|
|
35
|
+
addSpanEvent(name, 'info', message ?? '', attrs)
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Register global `caches` object (CacheStorage) with tracing
|
|
40
|
+
const rawCacheStorage = new SqliteCacheStorage(getDatabase())
|
|
41
|
+
const cacheMethods = ['match', 'put', 'delete']
|
|
42
|
+
|
|
43
|
+
// Instrument the default cache
|
|
44
|
+
rawCacheStorage.default = instrumentBinding(rawCacheStorage.default, {
|
|
45
|
+
type: 'cache',
|
|
46
|
+
name: 'default',
|
|
47
|
+
methods: cacheMethods,
|
|
48
|
+
}) as typeof rawCacheStorage.default
|
|
49
|
+
|
|
50
|
+
// Wrap open() to return instrumented caches
|
|
51
|
+
const originalOpen = rawCacheStorage.open.bind(rawCacheStorage)
|
|
52
|
+
rawCacheStorage.open = async (cacheName: string) => {
|
|
53
|
+
const cache = await originalOpen(cacheName)
|
|
54
|
+
return instrumentBinding(cache, {
|
|
55
|
+
type: 'cache',
|
|
56
|
+
name: cacheName,
|
|
57
|
+
methods: cacheMethods,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Object.defineProperty(globalThis, 'caches', {
|
|
62
|
+
value: rawCacheStorage,
|
|
63
|
+
writable: false,
|
|
64
|
+
configurable: true,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Register global `HTMLRewriter` class
|
|
68
|
+
Object.defineProperty(globalThis, 'HTMLRewriter', {
|
|
69
|
+
value: HTMLRewriter,
|
|
70
|
+
writable: false,
|
|
71
|
+
configurable: true,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Register global `WebSocketPair` class
|
|
75
|
+
Object.defineProperty(globalThis, 'WebSocketPair', {
|
|
76
|
+
value: WebSocketPair,
|
|
77
|
+
writable: false,
|
|
78
|
+
configurable: true,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Register global `WebSocketRequestResponsePair` class (used by DO hibernation API)
|
|
82
|
+
Object.defineProperty(globalThis, 'WebSocketRequestResponsePair', {
|
|
83
|
+
value: WebSocketRequestResponsePair,
|
|
84
|
+
writable: false,
|
|
85
|
+
configurable: true,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Patch Response to preserve CF-specific `webSocket` property from init
|
|
89
|
+
const OriginalResponse = globalThis.Response
|
|
90
|
+
globalThis.Response = class extends OriginalResponse {
|
|
91
|
+
webSocket?: InstanceType<typeof WebSocketPair>[0]
|
|
92
|
+
|
|
93
|
+
constructor(body?: any, init?: ResponseInit & { webSocket?: InstanceType<typeof WebSocketPair>[0] }) {
|
|
94
|
+
super(body, init)
|
|
95
|
+
if (init && 'webSocket' in init) {
|
|
96
|
+
this.webSocket = init.webSocket
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Register global CF stream classes
|
|
102
|
+
Object.defineProperty(globalThis, 'IdentityTransformStream', {
|
|
103
|
+
value: IdentityTransformStream,
|
|
104
|
+
writable: false,
|
|
105
|
+
configurable: true,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
Object.defineProperty(globalThis, 'FixedLengthStream', {
|
|
109
|
+
value: FixedLengthStream,
|
|
110
|
+
writable: false,
|
|
111
|
+
configurable: true,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// Patch crypto with CF-specific extensions (timingSafeEqual, DigestStream)
|
|
115
|
+
patchGlobalCrypto()
|
|
116
|
+
|
|
117
|
+
// Set navigator.userAgent to match Cloudflare Workers
|
|
118
|
+
Object.defineProperty(globalThis.navigator, 'userAgent', {
|
|
119
|
+
value: 'Cloudflare-Workers',
|
|
120
|
+
writable: false,
|
|
121
|
+
configurable: true,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// Set navigator.language (behind enable_navigator_language compat flag in CF)
|
|
125
|
+
if (!globalThis.navigator.language) {
|
|
126
|
+
Object.defineProperty(globalThis.navigator, 'language', {
|
|
127
|
+
value: 'en',
|
|
128
|
+
writable: false,
|
|
129
|
+
configurable: true,
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Set performance.timeOrigin to 0 (CF semantics)
|
|
134
|
+
Object.defineProperty(globalThis.performance, 'timeOrigin', {
|
|
135
|
+
value: 0,
|
|
136
|
+
writable: false,
|
|
137
|
+
configurable: true,
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// Register scheduler.wait(ms) — await-able setTimeout alternative
|
|
141
|
+
Object.defineProperty(globalThis, 'scheduler', {
|
|
142
|
+
value: {
|
|
143
|
+
wait(ms: number): Promise<void> {
|
|
144
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
writable: false,
|
|
148
|
+
configurable: true,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
@@ -19,7 +19,7 @@ class LopataDevEnvironment extends DevEnvironment {
|
|
|
19
19
|
|
|
20
20
|
get runner(): ModuleRunner {
|
|
21
21
|
if (!this._runner) {
|
|
22
|
-
this._runner = createServerModuleRunner(this, { hmr:
|
|
22
|
+
this._runner = createServerModuleRunner(this, { hmr: {} })
|
|
23
23
|
}
|
|
24
24
|
return this._runner
|
|
25
25
|
}
|
|
@@ -42,15 +42,19 @@ export function configPlugin(envName: string): Plugin {
|
|
|
42
42
|
name: 'lopata:config',
|
|
43
43
|
config() {
|
|
44
44
|
return {
|
|
45
|
+
// Disable Vite's SPA fallback (history API fallback) — Lopata handles
|
|
46
|
+
// all requests via worker.fetch(), so no index.html rewriting is needed.
|
|
47
|
+
appType: 'custom',
|
|
45
48
|
server: {
|
|
46
49
|
watch: {
|
|
47
|
-
ignored: ['**/.lopata/**'],
|
|
50
|
+
ignored: ['**/.lopata/**', '**/.wrangler/**', '**/.react-router/**'],
|
|
48
51
|
},
|
|
49
52
|
},
|
|
50
53
|
environments: {
|
|
51
54
|
[envName]: {
|
|
52
55
|
resolve: {
|
|
53
56
|
externalConditions: ['workerd', 'worker'],
|
|
57
|
+
dedupe: ['react', 'react-dom', 'react-router', 'react-router-dom'],
|
|
54
58
|
},
|
|
55
59
|
dev: {
|
|
56
60
|
createEnvironment(name, config) {
|