lopata 0.5.0 → 0.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lopata",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/config.ts CHANGED
@@ -80,8 +80,7 @@ export async function loadConfig(path: string, envName?: string): Promise<Wrangl
80
80
  if (path.endsWith('.toml')) {
81
81
  config = parseTOML(raw) as unknown as WranglerConfig
82
82
  } else {
83
- // JSON or JSONC — strip single-line comments (// ...) outside strings
84
- config = JSON.parse(stripJsoncComments(raw))
83
+ config = Bun.JSONC.parse(raw) as WranglerConfig
85
84
  }
86
85
  return applyEnvOverrides(config, envName)
87
86
  }
@@ -120,46 +119,3 @@ function applyEnvOverrides(config: WranglerConfig, envName?: string): WranglerCo
120
119
  }
121
120
  return merged
122
121
  }
123
-
124
- // ─── JSONC Comment Stripping ───────────────────────────────────────────────
125
-
126
- function stripJsoncComments(input: string): string {
127
- let result = ''
128
- let i = 0
129
- while (i < input.length) {
130
- // String literal — copy as-is
131
- if (input[i] === '"') {
132
- result += '"'
133
- i++
134
- while (i < input.length && input[i] !== '"') {
135
- if (input[i] === '\\') {
136
- result += input[i]! + (input[i + 1] ?? '')
137
- i += 2
138
- } else {
139
- result += input[i]!
140
- i++
141
- }
142
- }
143
- if (i < input.length) {
144
- result += '"'
145
- i++
146
- }
147
- continue
148
- }
149
- // Single-line comment
150
- if (input[i] === '/' && input[i + 1] === '/') {
151
- while (i < input.length && input[i] !== '\n') i++
152
- continue
153
- }
154
- // Block comment
155
- if (input[i] === '/' && input[i + 1] === '*') {
156
- i += 2
157
- while (i < input.length && !(input[i] === '*' && input[i + 1] === '/')) i++
158
- i += 2
159
- continue
160
- }
161
- result += input[i]!
162
- i++
163
- }
164
- return result
165
- }
package/src/env.ts CHANGED
@@ -355,9 +355,6 @@ export function buildEnv(
355
355
  }
356
356
  }
357
357
 
358
- // Store reference for cloudflare:workers env export
359
- setGlobalEnv(env)
360
-
361
358
  return { env, registry }
362
359
  }
363
360
 
@@ -146,10 +146,7 @@ export class GenerationManager {
146
146
  }
147
147
 
148
148
  private async _doReload(): Promise<Generation> {
149
- // 1. Import fresh worker module using cache-busting query string
150
- const workerModule = await import(`${this.workerPath}?v=${Date.now()}`)
151
-
152
- // 1b. Configure executor factory with module/config paths (for isolated mode)
149
+ // 1. Configure executor factory with module/config paths (for isolated mode)
153
150
  if (this.executorFactory && 'configure' in this.executorFactory) {
154
151
  ;(this.executorFactory as any).configure(this.workerPath, this._configPath)
155
152
  }
@@ -157,14 +154,19 @@ export class GenerationManager {
157
154
  // 2. Build new env with fresh binding instances (same underlying DB)
158
155
  const { env, registry } = buildEnv(this.config, this.baseDir, this.executorFactory, this.browserConfig)
159
156
 
160
- // 3. Wire DO and Workflow class references
161
- wireClassRefs(registry, workerModule, env, this.workerRegistry)
162
-
163
- // 4. Update globalEnv for cloudflare:workers env export (main worker only)
157
+ // 3. Update globalEnv BEFORE importing the worker module so that
158
+ // top-level `import { env } from "cloudflare:workers"` sees bindings
159
+ // during module evaluation (main worker only).
164
160
  if (this.isMain) {
165
161
  setGlobalEnv(env)
166
162
  }
167
163
 
164
+ // 4. Import fresh worker module using cache-busting query string
165
+ const workerModule = await import(`${this.workerPath}?v=${Date.now()}`)
166
+
167
+ // 5. Wire DO and Workflow class references
168
+ wireClassRefs(registry, workerModule, env, this.workerRegistry)
169
+
168
170
  // 5. Validate default export (or service worker fetch handler)
169
171
  const defaultExport = workerModule.default
170
172
  const classBasedExport = isEntrypointClass(defaultExport)
package/src/plugin.ts CHANGED
@@ -15,7 +15,21 @@ import { globalEnv } from './env'
15
15
  import { getActiveExecutionContext } from './execution-context'
16
16
  import { getActiveContext } from './tracing/context'
17
17
  import { instrumentBinding } from './tracing/instrument'
18
- import { addSpanEvent, setSpanAttribute, startSpan } from './tracing/span'
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
+ }
19
33
 
20
34
  // Register global `caches` object (CacheStorage) with tracing
21
35
  const rawCacheStorage = new SqliteCacheStorage(getDatabase())
@@ -151,6 +165,10 @@ for (const method of consoleMethods) {
151
165
  if (!ctx) return
152
166
  const message = args.map(formatConsoleArg).join(' ')
153
167
  addSpanEvent(`console.${method}`, method, message)
168
+ if (method === 'error') {
169
+ const errorArg = args.find((a) => a instanceof Error)
170
+ persistError(errorArg ?? new Error(message), 'console.error')
171
+ }
154
172
  }
155
173
  }
156
174
 
@@ -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