@vib3code/sdk 2.0.1
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 +118 -0
- package/DOCS/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +34 -0
- package/DOCS/CI_TESTING.md +38 -0
- package/DOCS/CLI_ONBOARDING.md +75 -0
- package/DOCS/CONTROL_REFERENCE.md +64 -0
- package/DOCS/DEV_TRACK_ANALYSIS.md +77 -0
- package/DOCS/DEV_TRACK_PLAN_2026-01-07.md +42 -0
- package/DOCS/DEV_TRACK_SESSION_2026-01-31.md +220 -0
- package/DOCS/ENV_SETUP.md +189 -0
- package/DOCS/EXPORT_FORMATS.md +417 -0
- package/DOCS/GPU_DISPOSAL_GUIDE.md +21 -0
- package/DOCS/LICENSING_TIERS.md +275 -0
- package/DOCS/MASTER_PLAN_2026-01-31.md +570 -0
- package/DOCS/OBS_SETUP_GUIDE.md +98 -0
- package/DOCS/PROJECT_SETUP.md +66 -0
- package/DOCS/RENDERER_LIFECYCLE.md +40 -0
- package/DOCS/REPO_MANIFEST.md +121 -0
- package/DOCS/SESSION_014_PLAN.md +195 -0
- package/DOCS/SESSION_LOG_2026-01-07.md +56 -0
- package/DOCS/STRATEGIC_BLUEPRINT_2026-01-07.md +72 -0
- package/DOCS/SYSTEM_AUDIT_2026-01-30.md +738 -0
- package/DOCS/SYSTEM_INVENTORY.md +520 -0
- package/DOCS/TELEMETRY_EXPORTS.md +34 -0
- package/DOCS/WEBGPU_STATUS.md +38 -0
- package/DOCS/XR_BENCHMARKS.md +608 -0
- package/LICENSE +21 -0
- package/README.md +426 -0
- package/docs/.nojekyll +0 -0
- package/docs/01-dissolution_of_euclidean_hegemony.html +346 -0
- package/docs/02-hyperspatial_ego_death.html +346 -0
- package/docs/03-post_cartesian_sublime.html +346 -0
- package/docs/04-crystalline_void_meditation.html +346 -0
- package/docs/05-quantum_decoherence_ballet.html +346 -0
- package/docs/06-dissolution_of_euclidean_hegemony.html +346 -0
- package/docs/07-hyperspatial_ego_death.html +346 -0
- package/docs/08-post_cartesian_sublime.html +346 -0
- package/docs/09-crystalline_void_meditation.html +346 -0
- package/docs/10-quantum_decoherence_ballet.html +346 -0
- package/docs/11-dissolution_of_euclidean_hegemony.html +346 -0
- package/docs/12-hyperspatial_ego_death.html +346 -0
- package/docs/13-post_cartesian_sublime.html +346 -0
- package/docs/index.html +794 -0
- package/docs/test-hub.html +441 -0
- package/docs/url-state.js +102 -0
- package/docs/vib3-exports/01-quantum-quantum-tetrahedron-lattice.html +489 -0
- package/docs/vib3-exports/02-quantum-quantum-hypersphere-matrix.html +489 -0
- package/docs/vib3-exports/03-quantum-quantum-hypertetra-fractal.html +489 -0
- package/docs/vib3-exports/04-faceted-faceted-crystal-structure.html +407 -0
- package/docs/vib3-exports/05-faceted-faceted-klein-bottle.html +407 -0
- package/docs/vib3-exports/06-faceted-faceted-hypertetra-torus.html +407 -0
- package/docs/vib3-exports/07-holographic-holographic-wave-field.html +457 -0
- package/docs/vib3-exports/08-holographic-holographic-hypersphere-sphere.html +457 -0
- package/docs/vib3-exports/09-holographic-holographic-hypertetra-crystal.html +457 -0
- package/docs/vib3-exports/index.html +238 -0
- package/docs/webgpu-live.html +702 -0
- package/package.json +367 -0
- package/src/advanced/AIPresetGenerator.js +777 -0
- package/src/advanced/MIDIController.js +703 -0
- package/src/advanced/OffscreenWorker.js +1051 -0
- package/src/advanced/WebGPUCompute.js +1051 -0
- package/src/advanced/WebXRRenderer.js +680 -0
- package/src/agent/cli/AgentCLI.js +615 -0
- package/src/agent/cli/index.js +14 -0
- package/src/agent/index.js +73 -0
- package/src/agent/mcp/MCPServer.js +950 -0
- package/src/agent/mcp/index.js +9 -0
- package/src/agent/mcp/tools.js +548 -0
- package/src/agent/telemetry/EventStream.js +669 -0
- package/src/agent/telemetry/Instrumentation.js +618 -0
- package/src/agent/telemetry/TelemetryExporters.js +427 -0
- package/src/agent/telemetry/TelemetryService.js +464 -0
- package/src/agent/telemetry/index.js +52 -0
- package/src/benchmarks/BenchmarkRunner.js +381 -0
- package/src/benchmarks/MetricsCollector.js +299 -0
- package/src/benchmarks/index.js +9 -0
- package/src/benchmarks/scenes.js +259 -0
- package/src/cli/index.js +675 -0
- package/src/config/ApiConfig.js +88 -0
- package/src/core/CanvasManager.js +217 -0
- package/src/core/ErrorReporter.js +117 -0
- package/src/core/ParameterMapper.js +333 -0
- package/src/core/Parameters.js +396 -0
- package/src/core/RendererContracts.js +200 -0
- package/src/core/UnifiedResourceManager.js +370 -0
- package/src/core/VIB3Engine.js +636 -0
- package/src/core/renderers/FacetedRendererAdapter.js +32 -0
- package/src/core/renderers/HolographicRendererAdapter.js +29 -0
- package/src/core/renderers/QuantumRendererAdapter.js +29 -0
- package/src/core/renderers/RendererLifecycleManager.js +63 -0
- package/src/creative/ColorPresetsSystem.js +980 -0
- package/src/creative/ParameterTimeline.js +1061 -0
- package/src/creative/PostProcessingPipeline.js +1113 -0
- package/src/creative/TransitionAnimator.js +683 -0
- package/src/export/CSSExporter.js +226 -0
- package/src/export/CardGeneratorBase.js +279 -0
- package/src/export/ExportManager.js +580 -0
- package/src/export/FacetedCardGenerator.js +279 -0
- package/src/export/HolographicCardGenerator.js +543 -0
- package/src/export/LottieExporter.js +552 -0
- package/src/export/QuantumCardGenerator.js +315 -0
- package/src/export/SVGExporter.js +519 -0
- package/src/export/ShaderExporter.js +903 -0
- package/src/export/TradingCardGenerator.js +3055 -0
- package/src/export/TradingCardManager.js +181 -0
- package/src/export/VIB3PackageExporter.js +559 -0
- package/src/export/index.js +14 -0
- package/src/export/systems/TradingCardSystemFaceted.js +494 -0
- package/src/export/systems/TradingCardSystemHolographic.js +452 -0
- package/src/export/systems/TradingCardSystemQuantum.js +411 -0
- package/src/faceted/FacetedSystem.js +963 -0
- package/src/features/CollectionManager.js +433 -0
- package/src/gallery/CollectionManager.js +240 -0
- package/src/gallery/GallerySystem.js +485 -0
- package/src/geometry/GeometryFactory.js +314 -0
- package/src/geometry/GeometryLibrary.js +72 -0
- package/src/geometry/buffers/BufferBuilder.js +338 -0
- package/src/geometry/buffers/index.js +18 -0
- package/src/geometry/generators/Crystal.js +420 -0
- package/src/geometry/generators/Fractal.js +298 -0
- package/src/geometry/generators/KleinBottle.js +197 -0
- package/src/geometry/generators/Sphere.js +192 -0
- package/src/geometry/generators/Tesseract.js +160 -0
- package/src/geometry/generators/Tetrahedron.js +225 -0
- package/src/geometry/generators/Torus.js +304 -0
- package/src/geometry/generators/Wave.js +341 -0
- package/src/geometry/index.js +142 -0
- package/src/geometry/warp/HypersphereCore.js +211 -0
- package/src/geometry/warp/HypertetraCore.js +386 -0
- package/src/geometry/warp/index.js +57 -0
- package/src/holograms/HolographicVisualizer.js +1073 -0
- package/src/holograms/RealHolographicSystem.js +966 -0
- package/src/holograms/variantRegistry.js +69 -0
- package/src/integrations/FigmaPlugin.js +854 -0
- package/src/integrations/OBSMode.js +754 -0
- package/src/integrations/ThreeJsPackage.js +660 -0
- package/src/integrations/TouchDesignerExport.js +552 -0
- package/src/integrations/frameworks/Vib3React.js +591 -0
- package/src/integrations/frameworks/Vib3Svelte.js +654 -0
- package/src/integrations/frameworks/Vib3Vue.js +628 -0
- package/src/llm/LLMParameterInterface.js +240 -0
- package/src/llm/LLMParameterUI.js +577 -0
- package/src/math/Mat4x4.js +708 -0
- package/src/math/Projection.js +341 -0
- package/src/math/Rotor4D.js +637 -0
- package/src/math/Vec4.js +476 -0
- package/src/math/constants.js +164 -0
- package/src/math/index.js +68 -0
- package/src/math/projections.js +54 -0
- package/src/math/rotations.js +196 -0
- package/src/quantum/QuantumEngine.js +906 -0
- package/src/quantum/QuantumVisualizer.js +1103 -0
- package/src/reactivity/ReactivityConfig.js +499 -0
- package/src/reactivity/ReactivityManager.js +586 -0
- package/src/reactivity/SpatialInputSystem.js +1783 -0
- package/src/reactivity/index.js +93 -0
- package/src/render/CommandBuffer.js +465 -0
- package/src/render/MultiCanvasBridge.js +340 -0
- package/src/render/RenderCommand.js +514 -0
- package/src/render/RenderResourceRegistry.js +523 -0
- package/src/render/RenderState.js +552 -0
- package/src/render/RenderTarget.js +512 -0
- package/src/render/ShaderLoader.js +253 -0
- package/src/render/ShaderProgram.js +599 -0
- package/src/render/UnifiedRenderBridge.js +496 -0
- package/src/render/backends/WebGLBackend.js +1108 -0
- package/src/render/backends/WebGPUBackend.js +1409 -0
- package/src/render/commands/CommandBufferExecutor.js +607 -0
- package/src/render/commands/RenderCommandBuffer.js +661 -0
- package/src/render/commands/index.js +17 -0
- package/src/render/index.js +367 -0
- package/src/scene/Disposable.js +498 -0
- package/src/scene/MemoryPool.js +618 -0
- package/src/scene/Node4D.js +697 -0
- package/src/scene/ResourceManager.js +599 -0
- package/src/scene/Scene4D.js +540 -0
- package/src/scene/index.js +98 -0
- package/src/schemas/error.schema.json +84 -0
- package/src/schemas/extension.schema.json +88 -0
- package/src/schemas/index.js +214 -0
- package/src/schemas/parameters.schema.json +142 -0
- package/src/schemas/tool-pack.schema.json +44 -0
- package/src/schemas/tool-response.schema.json +127 -0
- package/src/shaders/common/fullscreen.vert.glsl +5 -0
- package/src/shaders/common/fullscreen.vert.wgsl +17 -0
- package/src/shaders/common/geometry24.glsl +65 -0
- package/src/shaders/common/geometry24.wgsl +54 -0
- package/src/shaders/common/rotation4d.glsl +85 -0
- package/src/shaders/common/rotation4d.wgsl +86 -0
- package/src/shaders/common/uniforms.glsl +44 -0
- package/src/shaders/common/uniforms.wgsl +48 -0
- package/src/shaders/faceted/faceted.frag.glsl +129 -0
- package/src/shaders/faceted/faceted.frag.wgsl +164 -0
- package/src/shaders/holographic/holographic.frag.glsl +406 -0
- package/src/shaders/holographic/holographic.frag.wgsl +185 -0
- package/src/shaders/quantum/quantum.frag.glsl +513 -0
- package/src/shaders/quantum/quantum.frag.wgsl +361 -0
- package/src/testing/ParallelTestFramework.js +519 -0
- package/src/testing/__snapshots__/exportFormats.test.js.snap +24 -0
- package/src/testing/exportFormats.test.js +8 -0
- package/src/testing/projections.test.js +14 -0
- package/src/testing/rotations.test.js +37 -0
- package/src/ui/InteractivityMenu.js +516 -0
- package/src/ui/StatusManager.js +96 -0
- package/src/ui/adaptive/renderers/webgpu/BufferLayout.ts +252 -0
- package/src/ui/adaptive/renderers/webgpu/PolytopeInstanceBuffer.ts +144 -0
- package/src/ui/adaptive/renderers/webgpu/TripleBufferedUniform.ts +170 -0
- package/src/ui/adaptive/renderers/webgpu/WebGPURenderer.ts +735 -0
- package/src/ui/adaptive/renderers/webgpu/index.ts +112 -0
- package/src/variations/VariationManager.js +431 -0
- package/src/viewer/AudioReactivity.js +505 -0
- package/src/viewer/CardBending.js +481 -0
- package/src/viewer/GalleryUI.js +832 -0
- package/src/viewer/ReactivityManager.js +590 -0
- package/src/viewer/TradingCardExporter.js +600 -0
- package/src/viewer/ViewerPortal.js +374 -0
- package/src/viewer/index.js +12 -0
- package/src/wasm/WasmLoader.js +296 -0
- package/src/wasm/index.js +132 -0
- package/tools/agentic/mcpTools.js +88 -0
- package/tools/cli/agent-cli.js +92 -0
- package/tools/export/formats.js +24 -0
- package/tools/math/rotation-baseline.mjs +64 -0
- package/tools/shader-sync-verify.js +937 -0
- package/tools/telemetry/manifestPipeline.js +141 -0
- package/tools/telemetry/telemetryEvents.js +35 -0
- package/types/adaptive-sdk.d.ts +185 -0
- package/types/advanced/AIPresetGenerator.d.ts +81 -0
- package/types/advanced/MIDIController.d.ts +100 -0
- package/types/advanced/OffscreenWorker.d.ts +82 -0
- package/types/advanced/WebGPUCompute.d.ts +52 -0
- package/types/advanced/WebXRRenderer.d.ts +77 -0
- package/types/advanced/index.d.ts +46 -0
- package/types/core/ErrorReporter.d.ts +50 -0
- package/types/core/VIB3Engine.d.ts +204 -0
- package/types/creative/ColorPresetsSystem.d.ts +91 -0
- package/types/creative/ParameterTimeline.d.ts +74 -0
- package/types/creative/PostProcessingPipeline.d.ts +109 -0
- package/types/creative/TransitionAnimator.d.ts +71 -0
- package/types/creative/index.d.ts +35 -0
- package/types/integrations/FigmaPlugin.d.ts +46 -0
- package/types/integrations/OBSMode.d.ts +74 -0
- package/types/integrations/ThreeJsPackage.d.ts +62 -0
- package/types/integrations/TouchDesignerExport.d.ts +36 -0
- package/types/integrations/Vib3React.d.ts +74 -0
- package/types/integrations/Vib3Svelte.d.ts +63 -0
- package/types/integrations/Vib3Vue.d.ts +55 -0
- package/types/integrations/index.d.ts +52 -0
- package/types/reactivity/SpatialInputSystem.d.ts +173 -0
- package/types/reactivity/index.d.ts +394 -0
- package/types/render/CommandBuffer.d.ts +169 -0
- package/types/render/RenderCommand.d.ts +312 -0
- package/types/render/RenderState.d.ts +279 -0
- package/types/render/RenderTarget.d.ts +254 -0
- package/types/render/ShaderProgram.d.ts +277 -0
- package/types/render/UnifiedRenderBridge.d.ts +143 -0
- package/types/render/WebGLBackend.d.ts +168 -0
- package/types/render/WebGPUBackend.d.ts +186 -0
- package/types/render/index.d.ts +141 -0
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventStream.js - Server-Sent Events (SSE) System for Real-time Telemetry
|
|
3
|
+
*
|
|
4
|
+
* Provides real-time streaming of telemetry events to connected agents.
|
|
5
|
+
* Supports multiple channels, filtering, and backpressure handling.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { EventEmitter } from 'events';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Event types for the stream
|
|
12
|
+
*/
|
|
13
|
+
export const StreamEventType = {
|
|
14
|
+
TELEMETRY: 'telemetry',
|
|
15
|
+
SPAN_START: 'span:start',
|
|
16
|
+
SPAN_END: 'span:end',
|
|
17
|
+
METRIC: 'metric',
|
|
18
|
+
ERROR: 'error',
|
|
19
|
+
HEARTBEAT: 'heartbeat',
|
|
20
|
+
SYSTEM: 'system'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* SSE Event Stream Server
|
|
25
|
+
*
|
|
26
|
+
* Creates an SSE endpoint for streaming telemetry to agents.
|
|
27
|
+
*/
|
|
28
|
+
export class EventStreamServer extends EventEmitter {
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
super();
|
|
31
|
+
|
|
32
|
+
/** @type {Map<string, SSEConnection>} */
|
|
33
|
+
this.connections = new Map();
|
|
34
|
+
|
|
35
|
+
/** @type {number} Connection ID counter */
|
|
36
|
+
this.connectionIdCounter = 0;
|
|
37
|
+
|
|
38
|
+
/** @type {number} Heartbeat interval (ms) */
|
|
39
|
+
this.heartbeatInterval = options.heartbeatInterval || 30000;
|
|
40
|
+
|
|
41
|
+
/** @type {number} Max connections */
|
|
42
|
+
this.maxConnections = options.maxConnections || 100;
|
|
43
|
+
|
|
44
|
+
/** @type {number} Buffer size for each connection */
|
|
45
|
+
this.bufferSize = options.bufferSize || 100;
|
|
46
|
+
|
|
47
|
+
/** @type {boolean} Enable compression */
|
|
48
|
+
this.enableCompression = options.enableCompression || false;
|
|
49
|
+
|
|
50
|
+
/** @type {NodeJS.Timeout|null} */
|
|
51
|
+
this._heartbeatTimer = null;
|
|
52
|
+
|
|
53
|
+
/** @type {Array<{event: object, timestamp: number}>} */
|
|
54
|
+
this._replayBuffer = [];
|
|
55
|
+
|
|
56
|
+
/** @type {number} Replay buffer max size */
|
|
57
|
+
this._replayBufferSize = options.replayBufferSize || 1000;
|
|
58
|
+
|
|
59
|
+
this._startHeartbeat();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Handle incoming SSE connection request
|
|
64
|
+
* @param {object} req - HTTP request
|
|
65
|
+
* @param {object} res - HTTP response
|
|
66
|
+
* @param {object} options - Connection options
|
|
67
|
+
* @returns {string} Connection ID
|
|
68
|
+
*/
|
|
69
|
+
handleConnection(req, res, options = {}) {
|
|
70
|
+
if (this.connections.size >= this.maxConnections) {
|
|
71
|
+
res.writeHead(503, { 'Content-Type': 'text/plain' });
|
|
72
|
+
res.end('Max connections reached');
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const connectionId = `conn_${++this.connectionIdCounter}_${Date.now()}`;
|
|
77
|
+
|
|
78
|
+
// Set SSE headers
|
|
79
|
+
res.writeHead(200, {
|
|
80
|
+
'Content-Type': 'text/event-stream',
|
|
81
|
+
'Cache-Control': 'no-cache',
|
|
82
|
+
'Connection': 'keep-alive',
|
|
83
|
+
'Access-Control-Allow-Origin': options.cors || '*',
|
|
84
|
+
'X-Accel-Buffering': 'no' // Disable nginx buffering
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Create connection object
|
|
88
|
+
const connection = new SSEConnection({
|
|
89
|
+
id: connectionId,
|
|
90
|
+
response: res,
|
|
91
|
+
filters: options.filters || {},
|
|
92
|
+
channels: options.channels || ['*'],
|
|
93
|
+
bufferSize: this.bufferSize
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
this.connections.set(connectionId, connection);
|
|
97
|
+
|
|
98
|
+
// Send initial connection event
|
|
99
|
+
connection.send({
|
|
100
|
+
type: StreamEventType.SYSTEM,
|
|
101
|
+
event: 'connected',
|
|
102
|
+
connectionId,
|
|
103
|
+
timestamp: new Date().toISOString()
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Handle replay request
|
|
107
|
+
if (options.replay && options.lastEventId) {
|
|
108
|
+
this._replayEvents(connection, options.lastEventId);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Handle disconnect
|
|
112
|
+
req.on('close', () => {
|
|
113
|
+
this.connections.delete(connectionId);
|
|
114
|
+
this.emit('disconnect', connectionId);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
this.emit('connect', connectionId, connection);
|
|
118
|
+
return connectionId;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Broadcast event to all connections
|
|
123
|
+
* @param {object} event - Event to broadcast
|
|
124
|
+
* @param {string} channel - Optional channel filter
|
|
125
|
+
*/
|
|
126
|
+
broadcast(event, channel = 'default') {
|
|
127
|
+
const envelope = {
|
|
128
|
+
id: `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
129
|
+
channel,
|
|
130
|
+
timestamp: new Date().toISOString(),
|
|
131
|
+
...event
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Add to replay buffer
|
|
135
|
+
this._addToReplayBuffer(envelope);
|
|
136
|
+
|
|
137
|
+
// Send to all matching connections
|
|
138
|
+
for (const connection of this.connections.values()) {
|
|
139
|
+
if (connection.matchesChannel(channel) && connection.matchesFilters(event)) {
|
|
140
|
+
connection.send(envelope);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.emit('broadcast', envelope);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Send event to specific connection
|
|
149
|
+
* @param {string} connectionId
|
|
150
|
+
* @param {object} event
|
|
151
|
+
*/
|
|
152
|
+
sendTo(connectionId, event) {
|
|
153
|
+
const connection = this.connections.get(connectionId);
|
|
154
|
+
if (connection) {
|
|
155
|
+
connection.send({
|
|
156
|
+
id: `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
157
|
+
timestamp: new Date().toISOString(),
|
|
158
|
+
...event
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Stream telemetry event
|
|
165
|
+
* @param {object} telemetryEvent - From TelemetryService
|
|
166
|
+
*/
|
|
167
|
+
streamTelemetry(telemetryEvent) {
|
|
168
|
+
this.broadcast({
|
|
169
|
+
type: StreamEventType.TELEMETRY,
|
|
170
|
+
data: telemetryEvent
|
|
171
|
+
}, 'telemetry');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Stream span start
|
|
176
|
+
* @param {object} span
|
|
177
|
+
*/
|
|
178
|
+
streamSpanStart(span) {
|
|
179
|
+
this.broadcast({
|
|
180
|
+
type: StreamEventType.SPAN_START,
|
|
181
|
+
data: {
|
|
182
|
+
traceId: span.traceId,
|
|
183
|
+
spanId: span.spanId,
|
|
184
|
+
parentSpanId: span.parentSpanId,
|
|
185
|
+
name: span.name,
|
|
186
|
+
startTime: span.startTime,
|
|
187
|
+
attributes: span.attributes
|
|
188
|
+
}
|
|
189
|
+
}, 'spans');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Stream span end
|
|
194
|
+
* @param {object} span
|
|
195
|
+
*/
|
|
196
|
+
streamSpanEnd(span) {
|
|
197
|
+
this.broadcast({
|
|
198
|
+
type: StreamEventType.SPAN_END,
|
|
199
|
+
data: {
|
|
200
|
+
traceId: span.traceId,
|
|
201
|
+
spanId: span.spanId,
|
|
202
|
+
name: span.name,
|
|
203
|
+
status: span.status,
|
|
204
|
+
durationMs: span.durationMs,
|
|
205
|
+
endTime: span.endTime
|
|
206
|
+
}
|
|
207
|
+
}, 'spans');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Stream metric update
|
|
212
|
+
* @param {string} name - Metric name
|
|
213
|
+
* @param {number} value - Metric value
|
|
214
|
+
* @param {object} labels - Metric labels
|
|
215
|
+
*/
|
|
216
|
+
streamMetric(name, value, labels = {}) {
|
|
217
|
+
this.broadcast({
|
|
218
|
+
type: StreamEventType.METRIC,
|
|
219
|
+
data: { name, value, labels }
|
|
220
|
+
}, 'metrics');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Stream error
|
|
225
|
+
* @param {Error|object} error
|
|
226
|
+
*/
|
|
227
|
+
streamError(error) {
|
|
228
|
+
this.broadcast({
|
|
229
|
+
type: StreamEventType.ERROR,
|
|
230
|
+
data: {
|
|
231
|
+
message: error.message || String(error),
|
|
232
|
+
stack: error.stack,
|
|
233
|
+
code: error.code
|
|
234
|
+
}
|
|
235
|
+
}, 'errors');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get connection count
|
|
240
|
+
*/
|
|
241
|
+
get connectionCount() {
|
|
242
|
+
return this.connections.size;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get all connection IDs
|
|
247
|
+
*/
|
|
248
|
+
getConnectionIds() {
|
|
249
|
+
return Array.from(this.connections.keys());
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Close specific connection
|
|
254
|
+
* @param {string} connectionId
|
|
255
|
+
*/
|
|
256
|
+
closeConnection(connectionId) {
|
|
257
|
+
const connection = this.connections.get(connectionId);
|
|
258
|
+
if (connection) {
|
|
259
|
+
connection.close();
|
|
260
|
+
this.connections.delete(connectionId);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Close all connections and stop server
|
|
266
|
+
*/
|
|
267
|
+
close() {
|
|
268
|
+
if (this._heartbeatTimer) {
|
|
269
|
+
clearInterval(this._heartbeatTimer);
|
|
270
|
+
this._heartbeatTimer = null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
for (const connection of this.connections.values()) {
|
|
274
|
+
connection.close();
|
|
275
|
+
}
|
|
276
|
+
this.connections.clear();
|
|
277
|
+
this._replayBuffer = [];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Start heartbeat timer
|
|
282
|
+
* @private
|
|
283
|
+
*/
|
|
284
|
+
_startHeartbeat() {
|
|
285
|
+
this._heartbeatTimer = setInterval(() => {
|
|
286
|
+
const heartbeat = {
|
|
287
|
+
type: StreamEventType.HEARTBEAT,
|
|
288
|
+
timestamp: new Date().toISOString(),
|
|
289
|
+
connections: this.connections.size
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
for (const connection of this.connections.values()) {
|
|
293
|
+
connection.send(heartbeat);
|
|
294
|
+
}
|
|
295
|
+
}, this.heartbeatInterval);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Add event to replay buffer
|
|
300
|
+
* @private
|
|
301
|
+
*/
|
|
302
|
+
_addToReplayBuffer(event) {
|
|
303
|
+
this._replayBuffer.push({
|
|
304
|
+
event,
|
|
305
|
+
timestamp: Date.now()
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Trim buffer if needed
|
|
309
|
+
if (this._replayBuffer.length > this._replayBufferSize) {
|
|
310
|
+
this._replayBuffer.shift();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Replay missed events to connection
|
|
316
|
+
* @private
|
|
317
|
+
*/
|
|
318
|
+
_replayEvents(connection, lastEventId) {
|
|
319
|
+
// Find events after lastEventId
|
|
320
|
+
let found = false;
|
|
321
|
+
for (const { event } of this._replayBuffer) {
|
|
322
|
+
if (found) {
|
|
323
|
+
connection.send(event);
|
|
324
|
+
} else if (event.id === lastEventId) {
|
|
325
|
+
found = true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Individual SSE Connection
|
|
333
|
+
*/
|
|
334
|
+
export class SSEConnection {
|
|
335
|
+
constructor(options) {
|
|
336
|
+
this.id = options.id;
|
|
337
|
+
this.response = options.response;
|
|
338
|
+
this.filters = options.filters;
|
|
339
|
+
this.channels = new Set(options.channels);
|
|
340
|
+
this.bufferSize = options.bufferSize;
|
|
341
|
+
|
|
342
|
+
/** @type {Array<object>} Pending events buffer */
|
|
343
|
+
this._buffer = [];
|
|
344
|
+
|
|
345
|
+
/** @type {boolean} */
|
|
346
|
+
this._closed = false;
|
|
347
|
+
|
|
348
|
+
/** @type {number} Events sent counter */
|
|
349
|
+
this.eventsSent = 0;
|
|
350
|
+
|
|
351
|
+
/** @type {number} Creation time */
|
|
352
|
+
this.createdAt = Date.now();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Send event to connection
|
|
357
|
+
* @param {object} event
|
|
358
|
+
*/
|
|
359
|
+
send(event) {
|
|
360
|
+
if (this._closed) return;
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const data = this._formatSSE(event);
|
|
364
|
+
this.response.write(data);
|
|
365
|
+
this.eventsSent++;
|
|
366
|
+
} catch (err) {
|
|
367
|
+
// Connection likely closed
|
|
368
|
+
this._closed = true;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Check if connection subscribes to channel
|
|
374
|
+
* @param {string} channel
|
|
375
|
+
*/
|
|
376
|
+
matchesChannel(channel) {
|
|
377
|
+
return this.channels.has('*') || this.channels.has(channel);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Check if event matches connection filters
|
|
382
|
+
* @param {object} event
|
|
383
|
+
*/
|
|
384
|
+
matchesFilters(event) {
|
|
385
|
+
for (const [key, value] of Object.entries(this.filters)) {
|
|
386
|
+
if (event[key] !== undefined && event[key] !== value) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
// Check nested data
|
|
390
|
+
if (event.data && event.data[key] !== undefined && event.data[key] !== value) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Close connection
|
|
399
|
+
*/
|
|
400
|
+
close() {
|
|
401
|
+
if (!this._closed) {
|
|
402
|
+
this._closed = true;
|
|
403
|
+
try {
|
|
404
|
+
this.response.end();
|
|
405
|
+
} catch {
|
|
406
|
+
// Already closed
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Format event as SSE
|
|
413
|
+
* @private
|
|
414
|
+
*/
|
|
415
|
+
_formatSSE(event) {
|
|
416
|
+
let output = '';
|
|
417
|
+
|
|
418
|
+
if (event.id) {
|
|
419
|
+
output += `id: ${event.id}\n`;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (event.type) {
|
|
423
|
+
output += `event: ${event.type}\n`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Serialize data
|
|
427
|
+
const data = JSON.stringify(event);
|
|
428
|
+
output += `data: ${data}\n\n`;
|
|
429
|
+
|
|
430
|
+
return output;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Client-side EventStream consumer
|
|
436
|
+
*
|
|
437
|
+
* For browser/Node.js clients to consume SSE streams.
|
|
438
|
+
*/
|
|
439
|
+
export class EventStreamClient extends EventEmitter {
|
|
440
|
+
constructor(url, options = {}) {
|
|
441
|
+
super();
|
|
442
|
+
|
|
443
|
+
this.url = url;
|
|
444
|
+
this.options = options;
|
|
445
|
+
|
|
446
|
+
/** @type {EventSource|null} */
|
|
447
|
+
this._source = null;
|
|
448
|
+
|
|
449
|
+
/** @type {string|null} */
|
|
450
|
+
this._lastEventId = null;
|
|
451
|
+
|
|
452
|
+
/** @type {boolean} */
|
|
453
|
+
this._reconnecting = false;
|
|
454
|
+
|
|
455
|
+
/** @type {number} */
|
|
456
|
+
this._reconnectAttempts = 0;
|
|
457
|
+
|
|
458
|
+
/** @type {number} */
|
|
459
|
+
this.maxReconnectAttempts = options.maxReconnectAttempts || 10;
|
|
460
|
+
|
|
461
|
+
/** @type {number} Base reconnect delay (ms) */
|
|
462
|
+
this.reconnectDelay = options.reconnectDelay || 1000;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Connect to event stream
|
|
467
|
+
*/
|
|
468
|
+
connect() {
|
|
469
|
+
if (typeof EventSource === 'undefined') {
|
|
470
|
+
throw new Error('EventSource not available in this environment');
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const urlWithParams = new URL(this.url);
|
|
474
|
+
|
|
475
|
+
// Add channels if specified
|
|
476
|
+
if (this.options.channels) {
|
|
477
|
+
urlWithParams.searchParams.set('channels', this.options.channels.join(','));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Add last event ID for replay
|
|
481
|
+
if (this._lastEventId) {
|
|
482
|
+
urlWithParams.searchParams.set('lastEventId', this._lastEventId);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
this._source = new EventSource(urlWithParams.toString());
|
|
486
|
+
|
|
487
|
+
this._source.onopen = () => {
|
|
488
|
+
this._reconnectAttempts = 0;
|
|
489
|
+
this.emit('open');
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
this._source.onerror = (err) => {
|
|
493
|
+
this.emit('error', err);
|
|
494
|
+
this._handleReconnect();
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
this._source.onmessage = (event) => {
|
|
498
|
+
this._handleMessage(event);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
// Listen for specific event types
|
|
502
|
+
for (const type of Object.values(StreamEventType)) {
|
|
503
|
+
this._source.addEventListener(type, (event) => {
|
|
504
|
+
this._handleMessage(event);
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Disconnect from stream
|
|
511
|
+
*/
|
|
512
|
+
disconnect() {
|
|
513
|
+
if (this._source) {
|
|
514
|
+
this._source.close();
|
|
515
|
+
this._source = null;
|
|
516
|
+
}
|
|
517
|
+
this._reconnecting = false;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Handle incoming message
|
|
522
|
+
* @private
|
|
523
|
+
*/
|
|
524
|
+
_handleMessage(event) {
|
|
525
|
+
try {
|
|
526
|
+
const data = JSON.parse(event.data);
|
|
527
|
+
|
|
528
|
+
// Track last event ID
|
|
529
|
+
if (data.id) {
|
|
530
|
+
this._lastEventId = data.id;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Emit typed event
|
|
534
|
+
if (data.type) {
|
|
535
|
+
this.emit(data.type, data);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Also emit generic message
|
|
539
|
+
this.emit('message', data);
|
|
540
|
+
|
|
541
|
+
} catch (err) {
|
|
542
|
+
this.emit('parse-error', err, event.data);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Handle reconnection with exponential backoff
|
|
548
|
+
* @private
|
|
549
|
+
*/
|
|
550
|
+
_handleReconnect() {
|
|
551
|
+
if (this._reconnecting) return;
|
|
552
|
+
if (this._reconnectAttempts >= this.maxReconnectAttempts) {
|
|
553
|
+
this.emit('max-reconnect');
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
this._reconnecting = true;
|
|
558
|
+
this._reconnectAttempts++;
|
|
559
|
+
|
|
560
|
+
const delay = this.reconnectDelay * Math.pow(2, this._reconnectAttempts - 1);
|
|
561
|
+
|
|
562
|
+
setTimeout(() => {
|
|
563
|
+
this._reconnecting = false;
|
|
564
|
+
this.connect();
|
|
565
|
+
}, delay);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Get connection state
|
|
570
|
+
*/
|
|
571
|
+
get readyState() {
|
|
572
|
+
return this._source?.readyState ?? -1;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Check if connected
|
|
577
|
+
*/
|
|
578
|
+
get isConnected() {
|
|
579
|
+
return this._source?.readyState === 1; // EventSource.OPEN
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Create HTTP handler for SSE endpoint
|
|
585
|
+
*
|
|
586
|
+
* Use with Express, Koa, or native http server.
|
|
587
|
+
*
|
|
588
|
+
* @param {EventStreamServer} server
|
|
589
|
+
* @returns {function} HTTP handler
|
|
590
|
+
*/
|
|
591
|
+
export function createSSEHandler(server) {
|
|
592
|
+
return (req, res) => {
|
|
593
|
+
// Parse query parameters
|
|
594
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
595
|
+
const channels = url.searchParams.get('channels')?.split(',') || ['*'];
|
|
596
|
+
const lastEventId = url.searchParams.get('lastEventId') ||
|
|
597
|
+
req.headers['last-event-id'];
|
|
598
|
+
|
|
599
|
+
// Parse filters from query
|
|
600
|
+
const filters = {};
|
|
601
|
+
for (const [key, value] of url.searchParams) {
|
|
602
|
+
if (!['channels', 'lastEventId'].includes(key)) {
|
|
603
|
+
filters[key] = value;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
server.handleConnection(req, res, {
|
|
608
|
+
channels,
|
|
609
|
+
filters,
|
|
610
|
+
replay: !!lastEventId,
|
|
611
|
+
lastEventId
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Integration helper: Connect TelemetryService to EventStream
|
|
618
|
+
*
|
|
619
|
+
* @param {object} telemetryService - TelemetryService instance
|
|
620
|
+
* @param {EventStreamServer} streamServer - EventStreamServer instance
|
|
621
|
+
*/
|
|
622
|
+
export function connectTelemetryToStream(telemetryService, streamServer) {
|
|
623
|
+
// Wrap original methods to stream events
|
|
624
|
+
const originalRecordEvent = telemetryService.recordEvent?.bind(telemetryService);
|
|
625
|
+
const originalStartSpan = telemetryService.startSpan?.bind(telemetryService);
|
|
626
|
+
const originalEndSpan = telemetryService.endSpan?.bind(telemetryService);
|
|
627
|
+
|
|
628
|
+
if (originalRecordEvent) {
|
|
629
|
+
telemetryService.recordEvent = function(...args) {
|
|
630
|
+
const result = originalRecordEvent(...args);
|
|
631
|
+
const event = telemetryService.events?.[telemetryService.events.length - 1];
|
|
632
|
+
if (event) {
|
|
633
|
+
streamServer.streamTelemetry(event);
|
|
634
|
+
}
|
|
635
|
+
return result;
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (originalStartSpan) {
|
|
640
|
+
telemetryService.startSpan = function(...args) {
|
|
641
|
+
const span = originalStartSpan(...args);
|
|
642
|
+
streamServer.streamSpanStart(span);
|
|
643
|
+
return span;
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (originalEndSpan) {
|
|
648
|
+
telemetryService.endSpan = function(...args) {
|
|
649
|
+
const result = originalEndSpan(...args);
|
|
650
|
+
const span = telemetryService.activeSpans?.get(args[0]) ||
|
|
651
|
+
telemetryService._spans?.find(s => s.spanId === args[0]);
|
|
652
|
+
if (span) {
|
|
653
|
+
streamServer.streamSpanEnd(span);
|
|
654
|
+
}
|
|
655
|
+
return result;
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return streamServer;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export default {
|
|
663
|
+
StreamEventType,
|
|
664
|
+
EventStreamServer,
|
|
665
|
+
EventStreamClient,
|
|
666
|
+
SSEConnection,
|
|
667
|
+
createSSEHandler,
|
|
668
|
+
connectTelemetryToStream
|
|
669
|
+
};
|