@getvision/server 0.3.2 → 0.3.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @getvision/server
2
2
 
3
+ ## 0.3.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 22facf0: Implement Wide Events concept for logging
8
+
9
+ ## 0.3.3
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [2635948]
14
+ - @getvision/core@0.0.8
15
+
3
16
  ## 0.3.2
4
17
 
5
18
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvision/server",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "description": "Vision Server - Meta-framework with built-in observability, pub/sub, and type-safe APIs",
6
6
  "exports": {
package/src/service.ts CHANGED
@@ -445,6 +445,18 @@ export class ServiceBuilder<
445
445
  const { vision, traceId, rootSpanId } = visionCtx
446
446
  const tracer = vision.getTracer();
447
447
 
448
+ // Add addContext() method to context
449
+ (c as any).addContext = (context: Record<string, unknown>) => {
450
+ const current = getVisionContext()
451
+ // Use current traceId from context if available (handles nested spans/async correctly)
452
+ const targetTraceId = current?.traceId || traceId
453
+
454
+ const visionTrace = vision.getTraceStore().getTrace(targetTraceId)
455
+ if (visionTrace) {
456
+ visionTrace.metadata = { ...(visionTrace.metadata || {}), ...context }
457
+ }
458
+ }
459
+
448
460
  // Add span() method to context
449
461
  (c as any).span = <T>(
450
462
  name: string,
package/src/types.ts CHANGED
@@ -39,6 +39,13 @@ export interface ExtendedContext<
39
39
  attributes?: Record<string, any>,
40
40
  fn?: () => T
41
41
  ): T
42
+
43
+ /**
44
+ * Add context to the current active trace
45
+ * This is the "Wide Event" API - allowing adding high-cardinality data
46
+ * to the current request context.
47
+ */
48
+ addContext(context: Record<string, unknown>): void
42
49
 
43
50
  /**
44
51
  * Emit an event with type-safe validation
package/src/vision-app.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Hono } from 'hono'
2
2
  import type { Env, Schema, Input, MiddlewareHandler } from 'hono'
3
- import { VisionCore } from '@getvision/core'
3
+ import { VisionCore, runInTraceContext } from '@getvision/core'
4
4
  import type { RouteMetadata } from '@getvision/core'
5
5
  import { AsyncLocalStorage } from 'async_hooks'
6
6
  import { existsSync } from 'fs'
@@ -336,158 +336,172 @@ export class Vision<
336
336
  rootSpanId: ''
337
337
  },
338
338
  async () => {
339
- // Start main span
340
- const tracer = this.visionCore.getTracer()
341
- const rootSpan = tracer.startSpan('http.request', trace.id)
342
-
343
- // Update context with rootSpanId
344
- const ctx = visionContext.getStore()
345
- if (ctx) {
346
- ctx.rootSpanId = rootSpan.id
347
- }
339
+ // Also set core trace context so VisionCore.addContext() works
340
+ return runInTraceContext(trace.id, async () => {
341
+ // Start main span
342
+ const tracer = this.visionCore.getTracer()
343
+ const rootSpan = tracer.startSpan('http.request', trace.id)
344
+
345
+ // Update context with rootSpanId
346
+ const ctx = visionContext.getStore()
347
+ if (ctx) {
348
+ ctx.rootSpanId = rootSpan.id
349
+ }
348
350
 
349
- // Provide c.span globally for all downstream handlers (Vision/Hono sub-apps)
350
- if (!(c as any).span) {
351
- ;(c as any).span = <T>(
352
- name: string,
353
- attributes: Record<string, any> = {},
354
- fn?: () => T
355
- ): T => {
356
- const current = visionContext.getStore()
357
- const currentTraceId = current?.traceId || trace.id
358
- const currentRootSpanId = current?.rootSpanId || rootSpan.id
359
- const s = tracer.startSpan(name, currentTraceId, currentRootSpanId)
360
- for (const [k, v] of Object.entries(attributes)) tracer.setAttribute(s.id, k, v)
361
- try {
362
- const result = fn ? fn() : (undefined as any)
363
- const completed = tracer.endSpan(s.id)
364
- if (completed) this.visionCore.getTraceStore().addSpan(currentTraceId, completed)
365
- return result
366
- } catch (err) {
367
- tracer.setAttribute(s.id, 'error', true)
368
- tracer.setAttribute(s.id, 'error.message', err instanceof Error ? err.message : String(err))
369
- const completed = tracer.endSpan(s.id)
370
- if (completed) this.visionCore.getTraceStore().addSpan(currentTraceId, completed)
371
- throw err
351
+ // Provide c.span and c.addContext globally for all downstream handlers (Vision/Hono sub-apps)
352
+ if (!(c as any).span) {
353
+ (c as any).addContext = (context: Record<string, unknown>) => {
354
+ const current = visionContext.getStore()
355
+ const currentTraceId = current?.traceId || trace.id
356
+
357
+ // Add context to trace metadata via VisionCore
358
+ const visionTrace = this.visionCore.getTraceStore().getTrace(currentTraceId)
359
+ if (visionTrace) {
360
+ visionTrace.metadata = { ...(visionTrace.metadata || {}), ...context }
361
+ }
362
+ }
363
+
364
+ (c as any).span = <T>(
365
+ name: string,
366
+ attributes: Record<string, any> = {},
367
+ fn?: () => T
368
+ ): T => {
369
+ const current = visionContext.getStore()
370
+ const currentTraceId = current?.traceId || trace.id
371
+ const currentRootSpanId = current?.rootSpanId || rootSpan.id
372
+ const s = tracer.startSpan(name, currentTraceId, currentRootSpanId)
373
+ for (const [k, v] of Object.entries(attributes)) tracer.setAttribute(s.id, k, v)
374
+ try {
375
+ const result = fn ? fn() : (undefined as any)
376
+ const completed = tracer.endSpan(s.id)
377
+ if (completed) this.visionCore.getTraceStore().addSpan(currentTraceId, completed)
378
+ return result
379
+ } catch (err) {
380
+ tracer.setAttribute(s.id, 'error', true)
381
+ tracer.setAttribute(s.id, 'error.message', err instanceof Error ? err.message : String(err))
382
+ const completed = tracer.endSpan(s.id)
383
+ if (completed) this.visionCore.getTraceStore().addSpan(currentTraceId, completed)
384
+ throw err
385
+ }
372
386
  }
373
387
  }
374
- }
375
388
 
376
- // Add request attributes
377
- tracer.setAttribute(rootSpan.id, 'http.method', c.req.method)
378
- tracer.setAttribute(rootSpan.id, 'http.path', c.req.path)
379
- tracer.setAttribute(rootSpan.id, 'http.url', c.req.url)
380
-
381
- // Add query params if any
382
- const url = new URL(c.req.url)
383
- if (url.search) {
384
- tracer.setAttribute(rootSpan.id, 'http.query', url.search)
385
- }
389
+ // Add request attributes
390
+ tracer.setAttribute(rootSpan.id, 'http.method', c.req.method)
391
+ tracer.setAttribute(rootSpan.id, 'http.path', c.req.path)
392
+ tracer.setAttribute(rootSpan.id, 'http.url', c.req.url)
393
+
394
+ // Add query params if any
395
+ const url = new URL(c.req.url)
396
+ if (url.search) {
397
+ tracer.setAttribute(rootSpan.id, 'http.query', url.search)
398
+ }
386
399
 
387
- // Capture request metadata
388
- try {
389
- const rawReq = c.req.raw
390
- const headers: Record<string, string> = {}
391
- rawReq.headers.forEach((v, k) => { headers[k] = v })
392
-
393
- const urlObj = new URL(c.req.url)
394
- const query: Record<string, string> = {}
395
- urlObj.searchParams.forEach((v, k) => { query[k] = v })
400
+ // Capture request metadata
401
+ try {
402
+ const rawReq = c.req.raw
403
+ const headers: Record<string, string> = {}
404
+ rawReq.headers.forEach((v, k) => { headers[k] = v })
405
+
406
+ const urlObj = new URL(c.req.url)
407
+ const query: Record<string, string> = {}
408
+ urlObj.searchParams.forEach((v, k) => { query[k] = v })
409
+
410
+ let body: unknown = undefined
411
+ const ct = headers['content-type'] || headers['Content-Type']
412
+ if (ct && ct.includes('application/json')) {
413
+ try {
414
+ body = await rawReq.clone().json()
415
+ } catch {}
416
+ }
396
417
 
397
- let body: unknown = undefined
398
- const ct = headers['content-type'] || headers['Content-Type']
399
- if (ct && ct.includes('application/json')) {
400
- try {
401
- body = await rawReq.clone().json()
402
- } catch {}
403
- }
418
+ const sessionId = headers['x-vision-session']
419
+ if (sessionId) {
420
+ tracer.setAttribute(rootSpan.id, 'session.id', sessionId)
421
+ trace.metadata = { ...(trace.metadata || {}), sessionId }
422
+ }
404
423
 
405
- const sessionId = headers['x-vision-session']
406
- if (sessionId) {
407
- tracer.setAttribute(rootSpan.id, 'session.id', sessionId)
408
- trace.metadata = { ...(trace.metadata || {}), sessionId }
409
- }
424
+ const requestMeta = {
425
+ method: c.req.method,
426
+ url: urlObj.pathname + (urlObj.search || ''),
427
+ headers,
428
+ query: Object.keys(query).length ? query : undefined,
429
+ body,
430
+ }
431
+ tracer.setAttribute(rootSpan.id, 'http.request', requestMeta)
432
+ trace.metadata = { ...(trace.metadata || {}), request: requestMeta }
433
+
434
+ // Emit start log
435
+ if (logging) {
436
+ const parts = [
437
+ `method=${c.req.method}`,
438
+ `path=${c.req.path}`,
439
+ ]
440
+ if (sessionId) parts.push(`sessionId=${sessionId}`)
441
+ parts.push(`traceId=${trace.id}`)
442
+ console.info(`INF starting request ${parts.join(' ')}`)
443
+ }
410
444
 
411
- const requestMeta = {
412
- method: c.req.method,
413
- url: urlObj.pathname + (urlObj.search || ''),
414
- headers,
415
- query: Object.keys(query).length ? query : undefined,
416
- body,
417
- }
418
- tracer.setAttribute(rootSpan.id, 'http.request', requestMeta)
419
- trace.metadata = { ...(trace.metadata || {}), request: requestMeta }
420
-
421
- // Emit start log
422
- if (logging) {
423
- const parts = [
424
- `method=${c.req.method}`,
425
- `path=${c.req.path}`,
426
- ]
427
- if (sessionId) parts.push(`sessionId=${sessionId}`)
428
- parts.push(`traceId=${trace.id}`)
429
- console.info(`INF starting request ${parts.join(' ')}`)
430
- }
445
+ // Execute request
446
+ await next()
431
447
 
432
- // Execute request
433
- await next()
434
-
435
- // Add response attributes
436
- tracer.setAttribute(rootSpan.id, 'http.status_code', c.res.status)
437
- const resHeaders: Record<string, string> = {}
438
- c.res.headers?.forEach((v, k) => { resHeaders[k] = v as unknown as string })
448
+ // Add response attributes
449
+ tracer.setAttribute(rootSpan.id, 'http.status_code', c.res.status)
450
+ const resHeaders: Record<string, string> = {}
451
+ c.res.headers?.forEach((v, k) => { resHeaders[k] = v as unknown as string })
439
452
 
440
- let respBody: unknown = undefined
441
- const resCt = c.res.headers?.get('content-type') || ''
442
- try {
443
- const clone = c.res.clone()
444
- if (resCt.includes('application/json')) {
445
- const txt = await clone.text()
446
- if (txt && txt.length <= 65536) {
447
- try { respBody = JSON.parse(txt) } catch { respBody = txt }
453
+ let respBody: unknown = undefined
454
+ const resCt = c.res.headers?.get('content-type') || ''
455
+ try {
456
+ const clone = c.res.clone()
457
+ if (resCt.includes('application/json')) {
458
+ const txt = await clone.text()
459
+ if (txt && txt.length <= 65536) {
460
+ try { respBody = JSON.parse(txt) } catch { respBody = txt }
461
+ }
448
462
  }
463
+ } catch {}
464
+
465
+ const responseMeta = {
466
+ status: c.res.status,
467
+ headers: Object.keys(resHeaders).length ? resHeaders : undefined,
468
+ body: respBody,
469
+ }
470
+ tracer.setAttribute(rootSpan.id, 'http.response', responseMeta)
471
+ trace.metadata = { ...(trace.metadata || {}), response: responseMeta }
472
+
473
+ } catch (error) {
474
+ // Track error
475
+ tracer.addEvent(rootSpan.id, 'error', {
476
+ message: error instanceof Error ? error.message : 'Unknown error',
477
+ stack: error instanceof Error ? error.stack : undefined,
478
+ })
479
+
480
+ tracer.setAttribute(rootSpan.id, 'error', true)
481
+ throw error
482
+
483
+ } finally {
484
+ // End span and add it to trace
485
+ const completedSpan = tracer.endSpan(rootSpan.id)
486
+ if (completedSpan) {
487
+ this.visionCore.getTraceStore().addSpan(trace.id, completedSpan)
449
488
  }
450
- } catch {}
451
489
 
452
- const responseMeta = {
453
- status: c.res.status,
454
- headers: Object.keys(resHeaders).length ? resHeaders : undefined,
455
- body: respBody,
456
- }
457
- tracer.setAttribute(rootSpan.id, 'http.response', responseMeta)
458
- trace.metadata = { ...(trace.metadata || {}), response: responseMeta }
459
-
460
- } catch (error) {
461
- // Track error
462
- tracer.addEvent(rootSpan.id, 'error', {
463
- message: error instanceof Error ? error.message : 'Unknown error',
464
- stack: error instanceof Error ? error.stack : undefined,
465
- })
466
-
467
- tracer.setAttribute(rootSpan.id, 'error', true)
468
- throw error
469
-
470
- } finally {
471
- // End span and add it to trace
472
- const completedSpan = tracer.endSpan(rootSpan.id)
473
- if (completedSpan) {
474
- this.visionCore.getTraceStore().addSpan(trace.id, completedSpan)
475
- }
476
-
477
- // Complete trace
478
- const duration = Date.now() - startTime
479
- this.visionCore.completeTrace(trace.id, c.res.status, duration)
480
-
481
- // Add trace ID to response headers
482
- c.header('X-Vision-Trace-Id', trace.id)
483
-
484
- // Emit completion log
485
- if (logging) {
486
- console.info(
487
- `INF request completed code=${c.res.status} duration=${duration}ms method=${c.req.method} path=${c.req.path} traceId=${trace.id}`
488
- )
490
+ // Complete trace
491
+ const duration = Date.now() - startTime
492
+ this.visionCore.completeTrace(trace.id, c.res.status, duration)
493
+
494
+ // Add trace ID to response headers
495
+ c.header('X-Vision-Trace-Id', trace.id)
496
+
497
+ // Emit completion log
498
+ if (logging) {
499
+ console.info(
500
+ `INF request completed code=${c.res.status} duration=${duration}ms method=${c.req.method} path=${c.req.path} traceId=${trace.id}`
501
+ )
502
+ }
489
503
  }
490
- }
504
+ })
491
505
  }
492
506
  )
493
507
  })