shokupan 0.10.4 → 0.11.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.
Files changed (54) hide show
  1. package/dist/{analyzer-CKLGLFtx.cjs → analyzer-BAhvpNY_.cjs} +2 -7
  2. package/dist/{analyzer-CKLGLFtx.cjs.map → analyzer-BAhvpNY_.cjs.map} +1 -1
  3. package/dist/{analyzer-BqIe1p0R.js → analyzer-CnKnQ5KV.js} +3 -8
  4. package/dist/{analyzer-BqIe1p0R.js.map → analyzer-CnKnQ5KV.js.map} +1 -1
  5. package/dist/{analyzer.impl-D9Yi1Hax.cjs → analyzer.impl-CfpMu4-g.cjs} +586 -40
  6. package/dist/analyzer.impl-CfpMu4-g.cjs.map +1 -0
  7. package/dist/{analyzer.impl-CV6W1Eq7.js → analyzer.impl-DCiqlXI5.js} +586 -40
  8. package/dist/analyzer.impl-DCiqlXI5.js.map +1 -0
  9. package/dist/cli.cjs +206 -18
  10. package/dist/cli.cjs.map +1 -1
  11. package/dist/cli.js +206 -18
  12. package/dist/cli.js.map +1 -1
  13. package/dist/context.d.ts +6 -1
  14. package/dist/index.cjs +2405 -1008
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.js +2402 -1006
  17. package/dist/index.js.map +1 -1
  18. package/dist/plugins/application/api-explorer/static/explorer-client.mjs +423 -30
  19. package/dist/plugins/application/api-explorer/static/style.css +351 -10
  20. package/dist/plugins/application/api-explorer/static/theme.css +7 -2
  21. package/dist/plugins/application/asyncapi/generator.d.ts +4 -0
  22. package/dist/plugins/application/asyncapi/static/asyncapi-client.mjs +154 -22
  23. package/dist/plugins/application/asyncapi/static/style.css +24 -8
  24. package/dist/plugins/application/dashboard/fetch-interceptor.d.ts +107 -0
  25. package/dist/plugins/application/dashboard/metrics-collector.d.ts +38 -2
  26. package/dist/plugins/application/dashboard/plugin.d.ts +44 -1
  27. package/dist/plugins/application/dashboard/static/charts.js +127 -62
  28. package/dist/plugins/application/dashboard/static/client.js +160 -0
  29. package/dist/plugins/application/dashboard/static/graph.mjs +167 -56
  30. package/dist/plugins/application/dashboard/static/reactflow.css +20 -10
  31. package/dist/plugins/application/dashboard/static/registry.js +112 -8
  32. package/dist/plugins/application/dashboard/static/requests.js +868 -58
  33. package/dist/plugins/application/dashboard/static/styles.css +186 -14
  34. package/dist/plugins/application/dashboard/static/tabs.js +44 -9
  35. package/dist/plugins/application/dashboard/static/theme.css +7 -2
  36. package/dist/plugins/application/openapi/analyzer.impl.d.ts +61 -1
  37. package/dist/plugins/application/openapi/openapi.d.ts +3 -0
  38. package/dist/plugins/application/shared/ast-utils.d.ts +7 -0
  39. package/dist/router.d.ts +55 -16
  40. package/dist/shokupan.d.ts +7 -2
  41. package/dist/util/adapter/adapters.d.ts +19 -0
  42. package/dist/util/adapter/filesystem.d.ts +20 -0
  43. package/dist/util/controller-scanner.d.ts +4 -0
  44. package/dist/util/cpu-monitor.d.ts +2 -0
  45. package/dist/util/middleware-tracker.d.ts +10 -0
  46. package/dist/util/types.d.ts +37 -0
  47. package/package.json +5 -5
  48. package/dist/analyzer.impl-CV6W1Eq7.js.map +0 -1
  49. package/dist/analyzer.impl-D9Yi1Hax.cjs.map +0 -1
  50. package/dist/http-server-BEMPIs33.cjs +0 -85
  51. package/dist/http-server-BEMPIs33.cjs.map +0 -1
  52. package/dist/http-server-CCeagTyU.js +0 -68
  53. package/dist/http-server-CCeagTyU.js.map +0 -1
  54. package/dist/plugins/application/dashboard/static/poll.js +0 -146
@@ -10,9 +10,36 @@ const state = {
10
10
  logEntries: [],
11
11
  logAutoScroll: true,
12
12
  isConsoleMaximized: false,
13
- disableSourceView: !!window.DISABLE_SOURCE_VIEW
13
+ disableSourceView: !!window.DISABLE_SOURCE_VIEW,
14
+ // Layout State names
15
+ STORAGE_KEYS: {
16
+ SIDEBAR_WIDTH: 'asyncapi_sidebar_width',
17
+ CONSOLE_WIDTH: 'asyncapi_console_width',
18
+ NAV_COLLAPSED: 'asyncapi_nav_collapsed',
19
+ CONSOLE_COLLAPSED: 'asyncapi_console_collapsed',
20
+ CONSOLE_MAXIMIZED: 'asyncapi_console_maximized'
21
+ }
14
22
  };
15
23
 
24
+ const STORAGE_PREFIX = 'shokupan:asyncapi:';
25
+
26
+ function saveState(key, value) {
27
+ try {
28
+ localStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(value));
29
+ } catch (e) {
30
+ console.warn('Failed to save state', e);
31
+ }
32
+ }
33
+
34
+ function getState(key, defaultValue) {
35
+ try {
36
+ const item = localStorage.getItem(STORAGE_PREFIX + key);
37
+ return item ? JSON.parse(item) : defaultValue;
38
+ } catch (e) {
39
+ return defaultValue;
40
+ }
41
+ }
42
+
16
43
  const els = {
17
44
  url: document.getElementById('url'),
18
45
  protocol: document.getElementById('protocol'),
@@ -41,6 +68,7 @@ const els = {
41
68
  btnMaximizeConsole: document.getElementById('btn-maximize-console')
42
69
  };
43
70
 
71
+ // Resizers
44
72
  // Resizers
45
73
  function initResizers() {
46
74
  const setup = (id, varName, isLeft) => {
@@ -64,6 +92,10 @@ function initResizers() {
64
92
  document.removeEventListener('mouseup', onUp);
65
93
  document.body.style.cursor = '';
66
94
  el.classList.remove('resizing');
95
+
96
+ // Save new width
97
+ const finalW = parseInt(getComputedStyle(root).getPropertyValue(varName), 10);
98
+ saveState(varName === '--sidebar-width' ? state.STORAGE_KEYS.SIDEBAR_WIDTH : state.STORAGE_KEYS.CONSOLE_WIDTH, finalW);
67
99
  };
68
100
  document.addEventListener('mousemove', onMove);
69
101
  document.addEventListener('mouseup', onUp);
@@ -73,8 +105,10 @@ function initResizers() {
73
105
  setup('resizer-right', '--console-width', false);
74
106
  }
75
107
 
76
- function toggleConsoleMaximize() {
77
- state.isConsoleMaximized = !state.isConsoleMaximized;
108
+ function toggleConsoleMaximize(save = true) {
109
+ if (save) state.isConsoleMaximized = !state.isConsoleMaximized;
110
+ // implied else: state already toggled if called from restore
111
+
78
112
  const btn = els.btnMaximizeConsole;
79
113
 
80
114
  if (state.isConsoleMaximized) {
@@ -89,10 +123,6 @@ function toggleConsoleMaximize() {
89
123
  btn.title = "Restore Console";
90
124
  } else {
91
125
  // Restore
92
- els.mainWrapper.style.display = 'block'; // Or flex/whatever logic
93
- // Actually main-wrapper was display:flex via style attribute in HTML,
94
- // but let's check if we hid it. display='none' hides it.
95
- // We can just set it empty to revert to stylesheet or inline default.
96
126
  els.mainWrapper.style.display = '';
97
127
  els.resizerRight.style.display = 'block';
98
128
  els.consolePanel.style.flex = ''; // Revert to CSS default
@@ -102,6 +132,49 @@ function toggleConsoleMaximize() {
102
132
  btn.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect></svg>`;
103
133
  btn.title = "Maximize Console";
104
134
  }
135
+
136
+ if (save) {
137
+ saveState(state.STORAGE_KEYS.CONSOLE_MAXIMIZED, state.isConsoleMaximized);
138
+ }
139
+ }
140
+
141
+ function restoreLayout() {
142
+ const root = document.documentElement;
143
+
144
+ // Widths
145
+ const sidebarW = getState(state.STORAGE_KEYS.SIDEBAR_WIDTH, null);
146
+ if (sidebarW) root.style.setProperty('--sidebar-width', sidebarW + 'px');
147
+
148
+ const consoleW = getState(state.STORAGE_KEYS.CONSOLE_WIDTH, null);
149
+ if (consoleW) root.style.setProperty('--console-width', consoleW + 'px');
150
+
151
+ // Nav State
152
+ const navCollapsed = getState(state.STORAGE_KEYS.NAV_COLLAPSED, false);
153
+ if (navCollapsed && els.btnCollapseNav) {
154
+ els.sidebar.style.display = 'none';
155
+ els.resizerLeft.style.display = 'none';
156
+ els.btnExpandNav.style.display = 'flex';
157
+ // Ensure collapse button is hidden? Stylesheet handles it via display:none on expand usually?
158
+ // Based on original logic:
159
+ // collapse click -> sidebar none, resizer none, expand flex.
160
+ // expand click -> sidebar flex, resizer block, expand none.
161
+ // We assume collapse btn is always visible when sidebar is visible.
162
+ }
163
+
164
+ // Console State
165
+ const consoleCollapsed = getState(state.STORAGE_KEYS.CONSOLE_COLLAPSED, false);
166
+ if (consoleCollapsed && els.btnCollapseConsole) {
167
+ els.consolePanel.style.display = 'none';
168
+ els.resizerRight.style.display = 'none';
169
+ els.btnExpandConsole.style.display = 'flex';
170
+ }
171
+
172
+ // Maximize State
173
+ const consoleMaximized = getState(state.STORAGE_KEYS.CONSOLE_MAXIMIZED, false);
174
+ if (consoleMaximized && els.btnMaximizeConsole && !consoleCollapsed) { // Don't maximize if collapsed?
175
+ state.isConsoleMaximized = true;
176
+ toggleConsoleMaximize(false);
177
+ }
105
178
  }
106
179
 
107
180
  // Hydrate Navigation
@@ -165,15 +238,18 @@ require(['vs/editor/editor.main'], function () {
165
238
  // Auto-connect if URL is present (or wait for user?)
166
239
  // Original script called connect() immediately.
167
240
  // Toggles
241
+
168
242
  if (els.btnCollapseNav) els.btnCollapseNav.onclick = () => {
169
243
  els.sidebar.style.display = 'none';
170
244
  els.resizerLeft.style.display = 'none';
171
245
  els.btnExpandNav.style.display = 'flex';
246
+ saveState(state.STORAGE_KEYS.NAV_COLLAPSED, true);
172
247
  };
173
248
  if (els.btnExpandNav) els.btnExpandNav.onclick = () => {
174
249
  els.sidebar.style.display = 'flex';
175
250
  els.resizerLeft.style.display = 'block';
176
251
  els.btnExpandNav.style.display = 'none';
252
+ saveState(state.STORAGE_KEYS.NAV_COLLAPSED, false);
177
253
  };
178
254
 
179
255
  if (els.btnCollapseConsole) els.btnCollapseConsole.onclick = () => {
@@ -183,17 +259,21 @@ require(['vs/editor/editor.main'], function () {
183
259
  els.consolePanel.style.display = 'none';
184
260
  els.resizerRight.style.display = 'none';
185
261
  els.btnExpandConsole.style.display = 'flex';
262
+ saveState(state.STORAGE_KEYS.CONSOLE_COLLAPSED, true);
186
263
  };
187
264
  if (els.btnExpandConsole) els.btnExpandConsole.onclick = () => {
188
265
  els.consolePanel.style.display = 'flex';
189
266
  els.resizerRight.style.display = 'block';
190
267
  els.btnExpandConsole.style.display = 'none';
268
+ saveState(state.STORAGE_KEYS.CONSOLE_COLLAPSED, false);
191
269
 
192
- // Reset maximize state if it was maximized
270
+ // Reset maximize state if it was maximized (handled by collapse logic mostly, but good to be safe)
193
271
  if (state.isConsoleMaximized) toggleConsoleMaximize();
194
272
  };
195
273
 
196
- if (els.btnMaximizeConsole) els.btnMaximizeConsole.onclick = toggleConsoleMaximize;
274
+ if (els.btnMaximizeConsole) els.btnMaximizeConsole.onclick = () => toggleConsoleMaximize(true);
275
+
276
+ restoreLayout();
197
277
 
198
278
  connect();
199
279
  });
@@ -240,8 +320,8 @@ async function selectEvent(name, el) {
240
320
  if (!state.disableSourceView && sourceInfos.length > 0) {
241
321
  sourceLinksHtml = sourceInfos.map(s => {
242
322
  const filename = s.file ? s.file.split('/').pop() : 'unknown';
243
- return `<a href="vscode://file/${s.file}:${s.line}" style="color: #fbbf24; text-decoration: underline; font-family: monospace; display: block;">
244
- ${filename}:${s.line}
323
+ return `<a href="vscode://file/${s.file}:${s.line}" style="color: #fbbf24; text-decoration: none; display: block;" class="code-link">
324
+ <code style="font-family: 'JetBrains Mono', monospace; background: rgba(251, 191, 36, 0.1); padding: 2px 4px; border-radius: 4px;">${filename}:${s.line}</code>
245
325
  </a>`;
246
326
  }).join('');
247
327
  }
@@ -267,7 +347,7 @@ async function selectEvent(name, el) {
267
347
  </div>
268
348
 
269
349
  ${!state.disableSourceView && sourceInfos.length > 0 ? `
270
- <div class="section-title">Source Context</div>
350
+ <div class="section-title">Source Code</div>
271
351
  <div id="snippet-container"></div>
272
352
  ` : ''}
273
353
  </div>
@@ -282,7 +362,7 @@ async function selectEvent(name, el) {
282
362
  let code = null;
283
363
  if (src.file) {
284
364
  try {
285
- const res = await fetch(`./_code?file=${encodeURIComponent(src.file)}`);
365
+ const res = await fetch(`${window.BASE_PATH}/_code?file=${encodeURIComponent(src.file)}`);
286
366
  if (res.ok) code = await res.text();
287
367
  else code = `// Failed to load source: ${res.statusText}`;
288
368
  } catch (e) { code = `// Error loading source: ${e.message}`; }
@@ -290,9 +370,25 @@ async function selectEvent(name, el) {
290
370
 
291
371
  if (code) {
292
372
  const wrapper = document.createElement('div');
373
+ wrapper.id = `snippet-group-${i}`;
374
+ wrapper.classList.add('source-group');
293
375
  wrapper.style.marginBottom = '16px';
294
- wrapper.innerHTML = `<div style="font-size: 0.8rem; color: #888; margin-bottom: 4px;">${src.file.split('/').pop()}:${src.line}</div>
295
- <div id="snippet-editor-${i}" style="height: 300px; border: 1px solid #333; border-radius: 6px; overflow: hidden;"></div>`;
376
+
377
+ wrapper.innerHTML = `
378
+ <div class="source-header-actions" style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 8px;">
379
+ <a href="vscode://file/${src.file}:${src.line}" class="doc-source-link" title="${src.file}:${src.line}">
380
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:6px">
381
+ <polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline>
382
+ </svg>
383
+ ${src.file.split('/').pop()}:${src.line}
384
+ </a>
385
+ <button class="btn-icon" title="Toggle Fullscreen" onclick="toggleFullscreen('snippet-group-${i}', 'snippet-editor-${i}')">
386
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
387
+ <path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"></path>
388
+ </svg>
389
+ </button>
390
+ </div>
391
+ <div id="snippet-editor-${i}" style="height: 400px; border: 1px solid #333; border-radius: 6px; overflow: hidden;"></div>`;
296
392
  container.appendChild(wrapper);
297
393
 
298
394
  monaco.editor.colorize(code, 'typescript', {}).then(() => {
@@ -300,9 +396,6 @@ async function selectEvent(name, el) {
300
396
  if (!el) return;
301
397
 
302
398
  const model = monaco.editor.createModel(code, "typescript");
303
-
304
- el.style.height = '400px';
305
-
306
399
  const editor = monaco.editor.create(el, {
307
400
  model: model,
308
401
  readOnly: true,
@@ -342,6 +435,7 @@ async function selectEvent(name, el) {
342
435
  });
343
436
  }
344
437
  }
438
+
345
439
  }
346
440
  return;
347
441
  }
@@ -360,7 +454,7 @@ async function selectEvent(name, el) {
360
454
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:6px">
361
455
  <polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline>
362
456
  </svg>
363
- ${filename}:${s.line}
457
+ <code style="font-family: inherit;">${filename}:${s.line}</code>
364
458
  </a>`;
365
459
  } else {
366
460
  sourceLinkHtml = `<div class="doc-source-link" title="Multiple sources">
@@ -387,7 +481,7 @@ async function selectEvent(name, el) {
387
481
  ${desc ? `<p style="line-height: 1.6; margin-bottom: 2rem;">${desc}</p>` : ''}
388
482
 
389
483
  <div class="section-title">Payload Schema</div>
390
- ${payload ? renderSchemaToDOM(payload) : '<div class="empty-state-text" style="color:var(--text-muted); font-style:italic;">No payload definition.</div>'}
484
+ ${payload ? renderSchemaToDOM(payload) : '<div class="empty-state-text" style="color:var(--text-muted); font-style:italic;">Payload Unused</div>'}
391
485
 
392
486
  ${!state.disableSourceView && sourceInfos.length > 0 ? `
393
487
  <div class="section-title" style="margin-top: 24px;">Source Code</div>
@@ -455,7 +549,7 @@ async function selectEvent(name, el) {
455
549
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:6px">
456
550
  <polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline>
457
551
  </svg>
458
- ${fileName.split('/').pop()}:${sources[0].line}
552
+ <code style="font-family: inherit;">${fileName.split('/').pop()}:${sources[0].line}</code>
459
553
  </a>
460
554
  <button class="btn-icon" title="Toggle Fullscreen" onclick="toggleFullscreen('source-group-${i}', 'source-editor-${i}')">
461
555
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -469,7 +563,7 @@ async function selectEvent(name, el) {
469
563
  (async () => {
470
564
  let code = null;
471
565
  try {
472
- const res = await fetch(`./_code?file=${encodeURIComponent(fileName)}`);
566
+ const res = await fetch(`${window.BASE_PATH}/_code?file=${encodeURIComponent(fileName)}`);
473
567
  if (res.ok) code = await res.text();
474
568
  else code = `// Failed to load source: ${res.statusText}`;
475
569
  } catch (e) { code = `// Error loading source: ${e.message}`; }
@@ -746,3 +840,41 @@ els.sendBtn.onclick = () => {
746
840
  }
747
841
  } catch (e) { log('System', 'Invalid JSON', 'error'); }
748
842
  };
843
+
844
+ /* ================= Fullscreen Toggle ================= */
845
+ window.toggleFullscreen = function (containerId, editorId) {
846
+ const container = document.getElementById(containerId);
847
+ if (!container) return;
848
+
849
+ const isFullscreen = container.classList.toggle('fullscreen');
850
+
851
+ // Find the button to update icon
852
+ // The button is inside .source-header-actions
853
+ const btn = container.querySelector('button[title="Toggle Fullscreen"]') || container.querySelector('button[title="Exit Fullscreen"]');
854
+
855
+ if (btn) {
856
+ if (isFullscreen) {
857
+ btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"></path></svg>`;
858
+ btn.title = "Exit Fullscreen";
859
+ } else {
860
+ btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"></path></svg>`;
861
+ btn.title = "Toggle Fullscreen";
862
+ }
863
+ }
864
+
865
+ // ESC to exit
866
+ if (isFullscreen) {
867
+ const onEsc = (e) => {
868
+ if (e.key === 'Escape') {
869
+ window.toggleFullscreen(containerId, editorId);
870
+ }
871
+ };
872
+ container._escHandler = onEsc;
873
+ document.addEventListener('keydown', onEsc);
874
+ } else {
875
+ if (container._escHandler) {
876
+ document.removeEventListener('keydown', container._escHandler);
877
+ delete container._escHandler;
878
+ }
879
+ }
880
+ };
@@ -151,7 +151,7 @@ a {
151
151
  font-weight: 700;
152
152
  padding: 2px 6px;
153
153
  border-radius: 4px;
154
- font-family: 'JetBrains Mono', monospace;
154
+ font-family: var(--shokupan-font-mono);
155
155
  text-transform: uppercase;
156
156
  min-width: 38px;
157
157
  text-align: center;
@@ -215,7 +215,7 @@ a {
215
215
 
216
216
  /* Structured Schema View */
217
217
  .schema-root {
218
- font-family: 'JetBrains Mono', monospace;
218
+ font-family: var(--shokupan-font-mono);
219
219
  font-size: 0.9rem;
220
220
  background: var(--bg-card);
221
221
  border-radius: 8px;
@@ -290,7 +290,7 @@ a {
290
290
  border: none;
291
291
  color: var(--text-secondary);
292
292
  flex: 1;
293
- font-family: 'JetBrains Mono', monospace;
293
+ font-family: var(--shokupan-font-mono);
294
294
  font-size: 0.8rem;
295
295
  }
296
296
 
@@ -362,13 +362,9 @@ a {
362
362
  .logs-container {
363
363
  flex: 1;
364
364
  overflow-y: auto;
365
- padding: 1rem;
366
- font-family: 'JetBrains Mono', monospace;
365
+ font-family: var(--shokupan-font-mono);
367
366
  font-size: 0.8rem;
368
- display: block;
369
- /* flex-direction: column; gap: 0.5rem; -- removed for virtual scroll stability */
370
367
  position: relative;
371
- /* For absolute children */
372
368
  }
373
369
 
374
370
  .log-shim {
@@ -562,4 +558,24 @@ a {
562
558
 
563
559
  .floating-toggle.right {
564
560
  right: 10px;
561
+ }
562
+
563
+ /* Fullscreen Mode */
564
+ .source-group.fullscreen {
565
+ position: fixed;
566
+ top: 0;
567
+ left: 0;
568
+ width: 100vw;
569
+ height: 100vh;
570
+ z-index: 9999;
571
+ background: var(--bg-primary);
572
+ padding: 1rem;
573
+ border: none;
574
+ border-radius: 0;
575
+ display: flex !important;
576
+ /* Force display flex for fullscreen */
577
+ }
578
+
579
+ .source-group.fullscreen .source-header-actions {
580
+ margin-bottom: 1rem;
565
581
  }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Interface representing the details of an intercepted outbound request.
3
+ */
4
+ export interface OutboundRequestLog {
5
+ /**
6
+ * The HTTP method of the request (e.g., GET, POST).
7
+ */
8
+ method: string;
9
+ /**
10
+ * The full URL of the request.
11
+ */
12
+ url: string;
13
+ /**
14
+ * The request headers.
15
+ */
16
+ requestHeaders: Record<string, string>;
17
+ /**
18
+ * The HTTP status code of the response.
19
+ */
20
+ status: number;
21
+ /**
22
+ * The response headers.
23
+ */
24
+ responseHeaders: Record<string, string>;
25
+ /**
26
+ * The duration of the request in milliseconds.
27
+ */
28
+ duration: number;
29
+ /**
30
+ * The timestamp when the request started.
31
+ */
32
+ startTime: number;
33
+ /**
34
+ * The request body (if any).
35
+ */
36
+ requestBody?: any;
37
+ /**
38
+ * The response body (if any).
39
+ */
40
+ responseBody?: any;
41
+ /**
42
+ * The hostname of the request.
43
+ */
44
+ domain?: string;
45
+ /**
46
+ * The pathname of the request.
47
+ */
48
+ path?: string;
49
+ /**
50
+ * The protocol scheme (http/https) or version (1.1, 2.0).
51
+ */
52
+ protocol?: string;
53
+ /**
54
+ * The protocol scheme (http/https).
55
+ */
56
+ scheme?: string;
57
+ /**
58
+ * The remote IP address (if available).
59
+ */
60
+ remoteIP?: string;
61
+ /**
62
+ * The number of cookies sent.
63
+ */
64
+ cookies?: number;
65
+ /**
66
+ * The estimated transfer size in bytes.
67
+ */
68
+ transferred?: number;
69
+ }
70
+ /**
71
+ * A callback function type for handling captured outbound requests.
72
+ */
73
+ export type OutboundRequestCallback = (log: OutboundRequestLog) => void;
74
+ /**
75
+ * A utility class that intercepts calls to `global.fetch` to track outbound HTTP requests.
76
+ *
77
+ * @warning This class monkey-patches the global `fetch` function. While it attempts to transparently
78
+ * pass through all calls, it may have side effects on global state or other libraries that rely on
79
+ * the original `fetch`. Proceed with caution.
80
+ */
81
+ export declare class FetchInterceptor {
82
+ private originalFetch;
83
+ private originalHttpRequest;
84
+ private originalHttpsRequest;
85
+ private callbacks;
86
+ private isPatched;
87
+ constructor();
88
+ /**
89
+ * Patches the global `fetch` function to intercept requests.
90
+ * If already patched, this method does nothing.
91
+ */
92
+ patch(): void;
93
+ private patchGlobalFetch;
94
+ private patchNodeRequests;
95
+ /**
96
+ * Restores the original functions.
97
+ */
98
+ unpatch(): void;
99
+ /**
100
+ * Adds a callback to be notified of outbound requests.
101
+ * @param callback The callback function.
102
+ */
103
+ on(callback: OutboundRequestCallback): void;
104
+ private extractRequestMeta;
105
+ private processResponse;
106
+ private notify;
107
+ }
@@ -1,14 +1,50 @@
1
1
  import { SurrealDatastore } from '../../../util/datastore';
2
+ interface AggregatedMetric {
3
+ timestamp: number;
4
+ interval: string;
5
+ cpu: number;
6
+ memory: {
7
+ used: number;
8
+ total: number;
9
+ heapUsed: number;
10
+ heapTotal: number;
11
+ };
12
+ load: number[];
13
+ eventLoopLatency: {
14
+ min: number;
15
+ max: number;
16
+ mean: number;
17
+ p50: number;
18
+ p95: number;
19
+ p99: number;
20
+ };
21
+ requests: {
22
+ total: number;
23
+ rps: number;
24
+ success: number;
25
+ error: number;
26
+ };
27
+ responseTime: {
28
+ min: number;
29
+ max: number;
30
+ avg: number;
31
+ p50: number;
32
+ p95: number;
33
+ p99: number;
34
+ };
35
+ }
2
36
  export declare class MetricsCollector {
3
- private readonly db;
37
+ private onCollect?;
4
38
  private currentIntervalStart;
5
39
  private pendingDetails;
6
40
  private eventLoopHistogram;
7
41
  private timer;
8
- constructor(db: SurrealDatastore);
42
+ db?: SurrealDatastore;
43
+ constructor(db?: SurrealDatastore, onCollect?: (metric: AggregatedMetric) => void);
9
44
  recordRequest(duration: number, isError: boolean): void;
10
45
  private alignTimestamp;
11
46
  private collect;
12
47
  private flushInterval;
13
48
  stop(): void;
14
49
  }
50
+ export {};
@@ -1,4 +1,3 @@
1
- import { HeadersInit } from 'bun';
2
1
  import { $appRoot } from '../../../util/symbol';
3
2
  import { ShokupanHooks, ShokupanPlugin } from '../../../util/types';
4
3
  export interface RequestLog {
@@ -8,6 +7,21 @@ export interface RequestLog {
8
7
  duration: number;
9
8
  timestamp: number;
10
9
  handlerStack?: any[];
10
+ body?: any;
11
+ contentType?: string;
12
+ type: 'xhr' | 'fetch' | 'ws';
13
+ direction: 'inbound' | 'outbound';
14
+ size?: number;
15
+ protocol?: string;
16
+ domain?: string;
17
+ path?: string;
18
+ scheme?: string;
19
+ remoteIP?: string;
20
+ cookies?: number;
21
+ transferred?: number;
22
+ requestHeaders?: Record<string, string>;
23
+ responseHeaders?: Record<string, string>;
24
+ requestBody?: any;
11
25
  }
12
26
  export interface DashboardConfig {
13
27
  getRequestHeaders?: () => HeadersInit;
@@ -23,15 +37,35 @@ export interface DashboardConfig {
23
37
  asyncapi?: boolean | {
24
38
  path?: string;
25
39
  };
40
+ apiExplorer?: boolean | {
41
+ path?: string;
42
+ };
26
43
  };
44
+ /**
45
+ * Strategy for pushing request updates to the dashboard.
46
+ * 'immediate' - pushes every request as soon as it completes.
47
+ * 'batched' - buffers requests and pushes them at the interval specified by updateInterval.
48
+ * @default 'immediate'
49
+ */
50
+ updateStrategy?: 'immediate' | 'batched';
51
+ /**
52
+ * Interval in milliseconds for pushing batched updates.
53
+ * @default 10_000
54
+ */
55
+ updateInterval?: number;
27
56
  }
28
57
  export declare class Dashboard implements ShokupanPlugin {
29
58
  private readonly dashboardConfig;
30
59
  private [$appRoot];
31
60
  private router;
32
61
  private metrics;
62
+ private clients;
63
+ private broadcastTimer;
64
+ private requestPushTimer;
65
+ private requestsBuffer;
33
66
  private startTime;
34
67
  private instrumented;
68
+ private mountPath;
35
69
  private metricsCollector;
36
70
  get db(): import('../../../util/datastore').SurrealDatastore;
37
71
  constructor(dashboardConfig?: DashboardConfig);
@@ -41,12 +75,21 @@ export declare class Dashboard implements ShokupanPlugin {
41
75
  private detectIntegrations;
42
76
  private static getBasePath;
43
77
  private setupRoutes;
78
+ private getUptime;
79
+ private getPublicMetrics;
80
+ private broadcastMetricUpdate;
81
+ private sendHistory;
82
+ private broadcastMetrics;
44
83
  private instrumentApp;
45
84
  private assignIdsToRegistry;
46
85
  recordNodeMetric(id: string, type: string, duration: number, isError: boolean): void;
47
86
  recordEdgeMetric(from: string, to: string): void;
48
87
  private getLinkPattern;
49
88
  getHooks(): ShokupanHooks;
89
+ private startRequestPushTimer;
90
+ private broadcastRequestUpdates;
50
91
  private updateTiming;
92
+ private serializeHandlerStack;
93
+ private serializeBody;
51
94
  }
52
95
  export default function DebugDashboard(config?: DashboardConfig): Dashboard;