@djangocfg/debuger 2.1.219
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/README.md +345 -0
- package/dist/chunk-ESIQODSI.mjs +115 -0
- package/dist/chunk-ESIQODSI.mjs.map +1 -0
- package/dist/chunk-PAWJFY3S.mjs +6 -0
- package/dist/chunk-PAWJFY3S.mjs.map +1 -0
- package/dist/chunk-YQE3KTBG.mjs +64 -0
- package/dist/chunk-YQE3KTBG.mjs.map +1 -0
- package/dist/customEmitter-BO-1IWxm.d.ts +57 -0
- package/dist/emitters/index.cjs +74 -0
- package/dist/emitters/index.cjs.map +1 -0
- package/dist/emitters/index.d.ts +33 -0
- package/dist/emitters/index.mjs +4 -0
- package/dist/emitters/index.mjs.map +1 -0
- package/dist/index.cjs +887 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +162 -0
- package/dist/index.mjs +694 -0
- package/dist/index.mjs.map +1 -0
- package/dist/logger/index.cjs +123 -0
- package/dist/logger/index.cjs.map +1 -0
- package/dist/logger/index.d.ts +50 -0
- package/dist/logger/index.mjs +4 -0
- package/dist/logger/index.mjs.map +1 -0
- package/package.json +67 -0
- package/src/DebugButton.tsx +143 -0
- package/src/DebugPanel.tsx +171 -0
- package/src/bridges/index.ts +1 -0
- package/src/bridges/monitorBridge.ts +63 -0
- package/src/emitters/Emitter.ts +51 -0
- package/src/emitters/audioEmitter.ts +74 -0
- package/src/emitters/customEmitter.ts +42 -0
- package/src/emitters/index.ts +10 -0
- package/src/hooks/useAudioEventLog.ts +147 -0
- package/src/hooks/useCustomEventLog.ts +39 -0
- package/src/hooks/useDebugShortcut.ts +27 -0
- package/src/hooks/useStoreSnapshot.ts +70 -0
- package/src/index.ts +62 -0
- package/src/logger/index.ts +3 -0
- package/src/logger/logStore.ts +89 -0
- package/src/logger/logger.ts +95 -0
- package/src/logger/types.ts +39 -0
- package/src/panels/AudioDebugPanel.tsx +157 -0
- package/src/panels/LogsPanel.tsx +267 -0
- package/src/panels/StorePanel.tsx +71 -0
- package/src/store/debugStore.ts +42 -0
- package/src/styles/index.css +5 -0
package/README.md
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# @djangocfg/debuger
|
|
2
|
+
|
|
3
|
+
A universal floating debug panel for Next.js App Router projects. Drop-in overlay with structured logs, audio/media engine tracing, generic store inspection, and custom event channels.
|
|
4
|
+
|
|
5
|
+
Works in **dev** unconditionally. In **production** the panel is hidden behind a keyless unlock (URL param `?debug=1` or 5-click easter egg).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Monitor bridge** — auto-subscribes to `@djangocfg/monitor` store and forwards all captured events (JS errors, console, network, validation) into the Logs panel. Zero config — install both packages.
|
|
12
|
+
- **Logs panel** — virtualized log list from the built-in `useDebugLogStore`. Filter by level, component, free text. Export JSON. **No `@djangocfg/ui-core` dependency.**
|
|
13
|
+
- **Audio panel** — real-time audio/media engine event log. RAF-batched at 60 fps. Metrics: sync interval, seek rate, per-kind counters. `DEBUG_AUDIO` localStorage toggles. **Events buffered (200) so late-opening panel replays history.**
|
|
14
|
+
- **Store panel** — generic polling-based Zustand store viewer. Pass any `getState` fn; renders a collapsible JSON tree.
|
|
15
|
+
- **Custom tabs** — extend the panel with your own `CustomDebugTab[]` components.
|
|
16
|
+
- **Keyboard shortcut** — `Cmd+D` toggles the panel.
|
|
17
|
+
- **Production unlock** — `?debug=1` in URL (any value) or 5-click easter egg in bottom-left corner.
|
|
18
|
+
- **Zero-cost emitters** — `emit()` is a no-op when no listeners are registered. Safe to call in hot paths.
|
|
19
|
+
- **SSR-safe** — no browser APIs at import time.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add @djangocfg/debuger
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Peer dependencies:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pnpm add react react-dom zustand lucide-react @djangocfg/ui-core @djangocfg/ui-tools @tanstack/react-virtual
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Optional — for monitor bridge:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm add @djangocfg/monitor
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Tailwind v4
|
|
42
|
+
|
|
43
|
+
Add the styles import to your app's `globals.css` alongside other package styles:
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
@import "@djangocfg/ui-nextjs/styles";
|
|
47
|
+
@import "@djangocfg/layouts/styles";
|
|
48
|
+
@import "@djangocfg/ui-tools/styles";
|
|
49
|
+
@import "@djangocfg/debuger/styles"; /* ← add this */
|
|
50
|
+
@import "tailwindcss";
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// app/layout.tsx (or any client boundary)
|
|
59
|
+
import { DebugButton } from '@djangocfg/debuger';
|
|
60
|
+
|
|
61
|
+
export default function RootLayout({ children }) {
|
|
62
|
+
return (
|
|
63
|
+
<html>
|
|
64
|
+
<body>
|
|
65
|
+
{children}
|
|
66
|
+
<DebugButton />
|
|
67
|
+
</body>
|
|
68
|
+
</html>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`DebugButton` mounts the panel, registers `Cmd+D`, and handles URL/easter-egg unlock.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Production unlock
|
|
78
|
+
|
|
79
|
+
No secret key required. Three ways to unlock in production:
|
|
80
|
+
|
|
81
|
+
1. **URL param** — `?debug=1` (any non-empty value): panel opens and param is removed from URL.
|
|
82
|
+
2. **Easter egg** — click the bottom-left corner 5 times within 2 seconds.
|
|
83
|
+
3. **Keyboard** — `Cmd+D` works once unlocked via either method above.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Logger
|
|
88
|
+
|
|
89
|
+
The package ships its own logger — **no `@djangocfg/ui-core` dependency required**. Logs appear in the Logs panel automatically.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { createDebugLogger, debugLog } from '@djangocfg/debuger/logger';
|
|
93
|
+
|
|
94
|
+
// Reusable logger for a module
|
|
95
|
+
const log = createDebugLogger('MyComponent');
|
|
96
|
+
log.info('Mounted');
|
|
97
|
+
log.warn('Something looks off', { value: 42 });
|
|
98
|
+
log.error('Failed', { err: 'timeout' });
|
|
99
|
+
|
|
100
|
+
// One-shot helper (no instance needed)
|
|
101
|
+
debugLog('AudioEngine', 'debug', 'engine created');
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`LogLevel`: `'debug' | 'info' | 'warn' | 'error' | 'success'`
|
|
105
|
+
|
|
106
|
+
Access the store directly if needed:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { useDebugLogStore } from '@djangocfg/debuger/logger';
|
|
110
|
+
|
|
111
|
+
useDebugLogStore.getState().clearLogs();
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Custom tabs
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { DebugButton, StorePanel } from '@djangocfg/debuger';
|
|
120
|
+
import { Database } from 'lucide-react';
|
|
121
|
+
import { useMyStore } from '@/stores/myStore';
|
|
122
|
+
|
|
123
|
+
const myTabs = [
|
|
124
|
+
{
|
|
125
|
+
id: 'my-store',
|
|
126
|
+
label: 'My Store',
|
|
127
|
+
icon: Database,
|
|
128
|
+
panel: ({ isActive }) => (
|
|
129
|
+
<StorePanel
|
|
130
|
+
label="My Store"
|
|
131
|
+
getState={() => useMyStore.getState()}
|
|
132
|
+
isActive={isActive}
|
|
133
|
+
/>
|
|
134
|
+
),
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
<DebugButton panel={{ tabs: myTabs }} />
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Panel props
|
|
142
|
+
|
|
143
|
+
| Prop | Type | Default | Description |
|
|
144
|
+
|------|------|---------|-------------|
|
|
145
|
+
| `tabs` | `CustomDebugTab[]` | `[]` | Extra tabs appended after built-ins |
|
|
146
|
+
| `position` | `'bottom-left' \| 'bottom-right' \| 'top-left' \| 'top-right'` | `'bottom-left'` | Panel anchor |
|
|
147
|
+
| `defaultHeight` | `number` | `480` | Panel height in px |
|
|
148
|
+
| `defaultWidth` | `number` | `560` | Panel width in px |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Monitor Bridge
|
|
153
|
+
|
|
154
|
+
When `@djangocfg/monitor` is installed, the debug panel automatically bridges its event store into the Logs panel. No configuration needed — `DebugPanel` installs the subscription on mount.
|
|
155
|
+
|
|
156
|
+
Events appear with component names like `monitor:JS_ERROR`, `monitor:NETWORK_ERROR`, `monitor:WARNING`, etc.
|
|
157
|
+
|
|
158
|
+
You can also install the bridge manually if you don't use `DebugPanel` directly:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { installMonitorBridge } from '@djangocfg/debuger';
|
|
162
|
+
|
|
163
|
+
// Call once at app startup
|
|
164
|
+
installMonitorBridge();
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`@djangocfg/monitor` is an **optional peer** — if it's not installed, the bridge silently skips.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Emitters
|
|
172
|
+
|
|
173
|
+
Import from the tree-shakeable sub-entry (no React dependency):
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { emitAudioEvent, emitDebugEvent } from '@djangocfg/debuger/emitters';
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Audio emitter
|
|
180
|
+
|
|
181
|
+
Wire into your audio/media engine:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { emitAudioEvent, hasAudioListeners } from '@djangocfg/debuger/emitters';
|
|
185
|
+
|
|
186
|
+
// Hot-path guard: zero-cost when panel is closed
|
|
187
|
+
if (hasAudioListeners()) {
|
|
188
|
+
emitAudioEvent({ kind: 'sync', ts: Date.now(), trackId: 'abc', msg: `pos=${pos.toFixed(3)}` });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Lifecycle events — always emit (buffered for late subscribers)
|
|
192
|
+
emitAudioEvent({ kind: 'engine', ts: Date.now(), msg: 'engine created' });
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Events are **ring-buffered (200)** — opening the Audio panel replays recent history even if the engine started before the panel was opened.
|
|
196
|
+
|
|
197
|
+
**`AudioEventKind`**: `play` | `pause` | `seek` | `ended` | `sync` | `load` | `error` | `engine` | `custom`
|
|
198
|
+
|
|
199
|
+
**`AudioDebugEvent`** shape:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
interface AudioDebugEvent {
|
|
203
|
+
kind: AudioEventKind;
|
|
204
|
+
ts: number; // Date.now()
|
|
205
|
+
trackId?: string;
|
|
206
|
+
msg: string;
|
|
207
|
+
data?: Record<string, unknown>;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Custom channel emitter
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { emitDebugEvent } from '@djangocfg/debuger/emitters';
|
|
215
|
+
|
|
216
|
+
emitDebugEvent({
|
|
217
|
+
channel: 'pipeline', // matches your custom tab id
|
|
218
|
+
kind: 'slot:updated',
|
|
219
|
+
ts: Date.now(),
|
|
220
|
+
msg: 'Scene 3 slot updated',
|
|
221
|
+
data: { sceneId: '...', slot: 'image' },
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Use `useCustomEventLog(isActive, 'pipeline')` inside your custom tab to receive these events.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Hooks
|
|
230
|
+
|
|
231
|
+
### `useAudioEventLog(active)`
|
|
232
|
+
|
|
233
|
+
Subscribes to audio events. RAF-batched — no render storms at 60 fps. Replays buffered history on subscribe.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const { events, clear, seekRate, syncIntervalMs, kindCounts } = useAudioEventLog(isActive);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### `useCustomEventLog(active, channel?)`
|
|
240
|
+
|
|
241
|
+
Ring-buffer (300 events) for custom channel events.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
const { events, clear } = useCustomEventLog(isActive, 'pipeline');
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### `useStoreSnapshot(getState, intervalMs?, active?)`
|
|
248
|
+
|
|
249
|
+
Polling-based Zustand snapshot. Avoids reactive subscription issues with Zustand v5.
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
const snap = useStoreSnapshot(() => useMyStore.getState(), 200, isActive);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### `useDebugShortcut(options?)`
|
|
256
|
+
|
|
257
|
+
Registers a keyboard shortcut to toggle the panel. Called automatically inside `DebugButton`.
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
useDebugShortcut(); // default: Cmd+D (meta+d)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Generic `Emitter<TEvents>` class
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { Emitter } from '@djangocfg/debuger/emitters';
|
|
269
|
+
|
|
270
|
+
type MyEvents = {
|
|
271
|
+
'user:login': { id: string };
|
|
272
|
+
'page:view': { path: string };
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
export const myEmitter = new Emitter<MyEvents>();
|
|
276
|
+
|
|
277
|
+
const unsub = myEmitter.on('user:login', ({ id }) => console.log(id));
|
|
278
|
+
myEmitter.emit('user:login', { id: '123' });
|
|
279
|
+
unsub();
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Development playground
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
make playground
|
|
288
|
+
# or
|
|
289
|
+
cd playground && pnpm dev
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
The webpack config aliases `@djangocfg/debuger → src/` so no build step is needed during development.
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Build
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
pnpm build # tsup — ESM + CJS + .d.ts
|
|
300
|
+
pnpm dev # tsup --watch
|
|
301
|
+
pnpm check # tsc --noEmit
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Three entries produced:
|
|
305
|
+
|
|
306
|
+
| Entry | Output | Notes |
|
|
307
|
+
|-------|--------|-------|
|
|
308
|
+
| `src/index.ts` | `dist/index.{mjs,cjs,d.ts}` | React components, `'use client'` |
|
|
309
|
+
| `src/emitters/index.ts` | `dist/emitters/index.{mjs,cjs,d.ts}` | Pure TS, no React, tree-shakeable |
|
|
310
|
+
| `src/logger/index.ts` | `dist/logger/index.{mjs,cjs,d.ts}` | Logger + store, no ui-core dep |
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Package structure
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
src/
|
|
318
|
+
├── DebugButton.tsx # Floating trigger button + unlock logic
|
|
319
|
+
├── DebugPanel.tsx # Tab shell (Logs, Audio + custom tabs)
|
|
320
|
+
├── index.ts # Main entry — all exports
|
|
321
|
+
├── bridges/
|
|
322
|
+
│ ├── monitorBridge.ts # @djangocfg/monitor → logStore bridge (optional)
|
|
323
|
+
│ └── index.ts
|
|
324
|
+
├── emitters/
|
|
325
|
+
│ ├── Emitter.ts # Generic typed emitter class
|
|
326
|
+
│ ├── audioEmitter.ts # Audio/media event singleton + ring-buffer
|
|
327
|
+
│ ├── customEmitter.ts # Custom channel event singleton
|
|
328
|
+
│ └── index.ts # Emitters sub-entry
|
|
329
|
+
├── hooks/
|
|
330
|
+
│ ├── useAudioEventLog.ts # RAF-batched audio event log
|
|
331
|
+
│ ├── useCustomEventLog.ts # Custom channel event log
|
|
332
|
+
│ ├── useDebugShortcut.ts # Keyboard shortcut registration
|
|
333
|
+
│ └── useStoreSnapshot.ts # Polling Zustand snapshot
|
|
334
|
+
├── logger/
|
|
335
|
+
│ ├── types.ts # LogEntry, LogLevel, Logger types
|
|
336
|
+
│ ├── logStore.ts # useDebugLogStore (Zustand, no ui-core)
|
|
337
|
+
│ ├── logger.ts # createDebugLogger, debugLog
|
|
338
|
+
│ └── index.ts # Logger sub-entry
|
|
339
|
+
├── panels/
|
|
340
|
+
│ ├── AudioDebugPanel.tsx # Audio tab content
|
|
341
|
+
│ ├── LogsPanel.tsx # Logs tab (virtualized, uses own store)
|
|
342
|
+
│ └── StorePanel.tsx # Generic store viewer
|
|
343
|
+
└── store/
|
|
344
|
+
└── debugStore.ts # Zustand store: isOpen, tab, isUnlocked
|
|
345
|
+
```
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { __name } from './chunk-PAWJFY3S.mjs';
|
|
2
|
+
import { create } from 'zustand';
|
|
3
|
+
|
|
4
|
+
var MAX_LOGS = 1e3;
|
|
5
|
+
var DEFAULT_FILTER = {
|
|
6
|
+
levels: ["debug", "info", "warn", "error", "success"]
|
|
7
|
+
};
|
|
8
|
+
var _counter = 0;
|
|
9
|
+
function generateId() {
|
|
10
|
+
return `log-${Date.now()}-${++_counter}`;
|
|
11
|
+
}
|
|
12
|
+
__name(generateId, "generateId");
|
|
13
|
+
function matchesFilter(entry, filter) {
|
|
14
|
+
if (!filter.levels.includes(entry.level)) return false;
|
|
15
|
+
if (filter.component) {
|
|
16
|
+
if (!entry.component.toLowerCase().includes(filter.component.toLowerCase())) return false;
|
|
17
|
+
}
|
|
18
|
+
if (filter.search) {
|
|
19
|
+
const s = filter.search.toLowerCase();
|
|
20
|
+
const inMsg = entry.message.toLowerCase().includes(s);
|
|
21
|
+
const inData = entry.data ? JSON.stringify(entry.data).toLowerCase().includes(s) : false;
|
|
22
|
+
if (!inMsg && !inData) return false;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
__name(matchesFilter, "matchesFilter");
|
|
27
|
+
var useDebugLogStore = create((set, get) => ({
|
|
28
|
+
logs: [],
|
|
29
|
+
filter: DEFAULT_FILTER,
|
|
30
|
+
addLog: /* @__PURE__ */ __name((entry) => {
|
|
31
|
+
const newEntry = {
|
|
32
|
+
...entry,
|
|
33
|
+
id: generateId(),
|
|
34
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
35
|
+
};
|
|
36
|
+
set((state) => {
|
|
37
|
+
const next = [...state.logs, newEntry];
|
|
38
|
+
return { logs: next.length > MAX_LOGS ? next.slice(-MAX_LOGS) : next };
|
|
39
|
+
});
|
|
40
|
+
}, "addLog"),
|
|
41
|
+
clearLogs: /* @__PURE__ */ __name(() => set({ logs: [] }), "clearLogs"),
|
|
42
|
+
setFilter: /* @__PURE__ */ __name((filter) => set((state) => ({ filter: { ...state.filter, ...filter } })), "setFilter"),
|
|
43
|
+
getFilteredLogs: /* @__PURE__ */ __name(() => {
|
|
44
|
+
const { logs, filter } = get();
|
|
45
|
+
return logs.filter((e) => matchesFilter(e, filter));
|
|
46
|
+
}, "getFilteredLogs"),
|
|
47
|
+
exportLogs: /* @__PURE__ */ __name(() => JSON.stringify(get().logs, null, 2), "exportLogs")
|
|
48
|
+
}));
|
|
49
|
+
var useDebugFilteredLogs = /* @__PURE__ */ __name(() => {
|
|
50
|
+
const logs = useDebugLogStore((s) => s.logs);
|
|
51
|
+
const filter = useDebugLogStore((s) => s.filter);
|
|
52
|
+
return logs.filter((e) => matchesFilter(e, filter));
|
|
53
|
+
}, "useDebugFilteredLogs");
|
|
54
|
+
var useDebugLogCount = /* @__PURE__ */ __name(() => useDebugLogStore((s) => s.logs.length), "useDebugLogCount");
|
|
55
|
+
var useDebugErrorCount = /* @__PURE__ */ __name(() => useDebugLogStore((s) => s.logs.filter((l) => l.level === "error").length), "useDebugErrorCount");
|
|
56
|
+
var isBrowser = typeof window !== "undefined";
|
|
57
|
+
function extractStack(data) {
|
|
58
|
+
if (!data) return { cleanData: void 0, stack: void 0 };
|
|
59
|
+
const cleanData = { ...data };
|
|
60
|
+
let stack;
|
|
61
|
+
if (data.error instanceof Error) {
|
|
62
|
+
stack = data.error.stack;
|
|
63
|
+
cleanData.error = { name: data.error.name, message: data.error.message };
|
|
64
|
+
} else if (typeof data.error === "object" && data.error !== null) {
|
|
65
|
+
const e = data.error;
|
|
66
|
+
if (typeof e.stack === "string") stack = e.stack;
|
|
67
|
+
if (typeof e.message === "string") cleanData.error = e.message;
|
|
68
|
+
}
|
|
69
|
+
return { cleanData, stack };
|
|
70
|
+
}
|
|
71
|
+
__name(extractStack, "extractStack");
|
|
72
|
+
function createDebugLogger(component) {
|
|
73
|
+
const write = /* @__PURE__ */ __name((level, message, data) => {
|
|
74
|
+
const { cleanData, stack } = extractStack(data);
|
|
75
|
+
if (isBrowser) {
|
|
76
|
+
useDebugLogStore.getState().addLog({ level, component, message, data: cleanData, stack });
|
|
77
|
+
}
|
|
78
|
+
{
|
|
79
|
+
const method = level === "error" ? "error" : level === "warn" ? "warn" : "log";
|
|
80
|
+
const prefix = `[${component}]`;
|
|
81
|
+
if (cleanData) {
|
|
82
|
+
console[method](prefix, message, cleanData);
|
|
83
|
+
} else {
|
|
84
|
+
console[method](prefix, message);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}, "write");
|
|
88
|
+
return {
|
|
89
|
+
debug: /* @__PURE__ */ __name((msg, data) => write("debug", msg, data), "debug"),
|
|
90
|
+
info: /* @__PURE__ */ __name((msg, data) => write("info", msg, data), "info"),
|
|
91
|
+
warn: /* @__PURE__ */ __name((msg, data) => write("warn", msg, data), "warn"),
|
|
92
|
+
error: /* @__PURE__ */ __name((msg, data) => write("error", msg, data), "error"),
|
|
93
|
+
success: /* @__PURE__ */ __name((msg, data) => write("success", msg, data), "success")
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
__name(createDebugLogger, "createDebugLogger");
|
|
97
|
+
function debugLog(component, level, message, data) {
|
|
98
|
+
const { cleanData, stack } = extractStack(data);
|
|
99
|
+
if (isBrowser) {
|
|
100
|
+
useDebugLogStore.getState().addLog({ level, component, message, data: cleanData, stack });
|
|
101
|
+
}
|
|
102
|
+
{
|
|
103
|
+
const method = level === "error" ? "error" : level === "warn" ? "warn" : "log";
|
|
104
|
+
if (cleanData) {
|
|
105
|
+
console[method](`[${component}]`, message, cleanData);
|
|
106
|
+
} else {
|
|
107
|
+
console[method](`[${component}]`, message);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
__name(debugLog, "debugLog");
|
|
112
|
+
|
|
113
|
+
export { createDebugLogger, debugLog, useDebugErrorCount, useDebugFilteredLogs, useDebugLogCount, useDebugLogStore };
|
|
114
|
+
//# sourceMappingURL=chunk-ESIQODSI.mjs.map
|
|
115
|
+
//# sourceMappingURL=chunk-ESIQODSI.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logger/logStore.ts","../src/logger/logger.ts"],"names":[],"mappings":"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","file":"chunk-ESIQODSI.mjs","sourcesContent":["'use client';\n\nimport { create } from 'zustand';\nimport type { LogStore, LogEntry, LogFilter } from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst MAX_LOGS = 1000;\n\nconst DEFAULT_FILTER: LogFilter = {\n levels: ['debug', 'info', 'warn', 'error', 'success'],\n};\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nlet _counter = 0;\n\nfunction generateId(): string {\n return `log-${Date.now()}-${++_counter}`;\n}\n\nfunction matchesFilter(entry: LogEntry, filter: LogFilter): boolean {\n if (!filter.levels.includes(entry.level)) return false;\n\n if (filter.component) {\n if (!entry.component.toLowerCase().includes(filter.component.toLowerCase())) return false;\n }\n\n if (filter.search) {\n const s = filter.search.toLowerCase();\n const inMsg = entry.message.toLowerCase().includes(s);\n const inData = entry.data ? JSON.stringify(entry.data).toLowerCase().includes(s) : false;\n if (!inMsg && !inData) return false;\n }\n\n return true;\n}\n\n// ============================================================================\n// Store\n// ============================================================================\n\nexport const useDebugLogStore = create<LogStore>((set, get) => ({\n logs: [],\n filter: DEFAULT_FILTER,\n\n addLog: (entry) => {\n const newEntry: LogEntry = {\n ...entry,\n id: generateId(),\n timestamp: new Date(),\n };\n set((state) => {\n const next = [...state.logs, newEntry];\n return { logs: next.length > MAX_LOGS ? next.slice(-MAX_LOGS) : next };\n });\n },\n\n clearLogs: () => set({ logs: [] }),\n\n setFilter: (filter) =>\n set((state) => ({ filter: { ...state.filter, ...filter } })),\n\n getFilteredLogs: () => {\n const { logs, filter } = get();\n return logs.filter((e) => matchesFilter(e, filter));\n },\n\n exportLogs: () => JSON.stringify(get().logs, null, 2),\n}));\n\n// ============================================================================\n// Selector hooks\n// ============================================================================\n\nexport const useDebugFilteredLogs = () => {\n const logs = useDebugLogStore((s) => s.logs);\n const filter = useDebugLogStore((s) => s.filter);\n return logs.filter((e) => matchesFilter(e, filter));\n};\n\nexport const useDebugLogCount = () => useDebugLogStore((s) => s.logs.length);\n\nexport const useDebugErrorCount = () =>\n useDebugLogStore((s) => s.logs.filter((l) => l.level === 'error').length);\n","/**\n * Debug Logger\n *\n * Standalone logger for @djangocfg/debuger — no ui-core dependency.\n * Writes to useDebugLogStore (shown in LogsPanel) and to console in dev.\n */\n\nimport { useDebugLogStore } from './logStore';\nimport type { Logger, LogLevel } from './types';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nconst isDev = process.env.NODE_ENV !== 'production';\nconst isBrowser = typeof window !== 'undefined';\n\nfunction extractStack(data?: Record<string, unknown>): {\n cleanData: Record<string, unknown> | undefined;\n stack: string | undefined;\n} {\n if (!data) return { cleanData: undefined, stack: undefined };\n\n const cleanData = { ...data };\n let stack: string | undefined;\n\n if (data.error instanceof Error) {\n stack = data.error.stack;\n cleanData.error = { name: data.error.name, message: data.error.message };\n } else if (typeof data.error === 'object' && data.error !== null) {\n const e = data.error as Record<string, unknown>;\n if (typeof e.stack === 'string') stack = e.stack;\n if (typeof e.message === 'string') cleanData.error = e.message;\n }\n\n return { cleanData, stack };\n}\n\n// ============================================================================\n// createLogger\n// ============================================================================\n\nexport function createDebugLogger(component: string): Logger {\n const write = (level: LogLevel, message: string, data?: Record<string, unknown>) => {\n const { cleanData, stack } = extractStack(data);\n\n if (isBrowser) {\n useDebugLogStore.getState().addLog({ level, component, message, data: cleanData, stack });\n }\n\n if (isDev) {\n const method = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log';\n const prefix = `[${component}]`;\n if (cleanData) {\n console[method](prefix, message, cleanData);\n } else {\n console[method](prefix, message);\n }\n }\n };\n\n return {\n debug: (msg, data) => write('debug', msg, data),\n info: (msg, data) => write('info', msg, data),\n warn: (msg, data) => write('warn', msg, data),\n error: (msg, data) => write('error', msg, data),\n success: (msg, data) => write('success', msg, data),\n };\n}\n\n// ============================================================================\n// debugLog — one-shot helper (creates logger on each call, for non-hot paths)\n// ============================================================================\n\nexport function debugLog(\n component: string,\n level: LogLevel,\n message: string,\n data?: Record<string, unknown>,\n): void {\n const { cleanData, stack } = extractStack(data);\n\n if (isBrowser) {\n useDebugLogStore.getState().addLog({ level, component, message, data: cleanData, stack });\n }\n\n if (isDev) {\n const method = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log';\n if (cleanData) {\n console[method](`[${component}]`, message, cleanData);\n } else {\n console[method](`[${component}]`, message);\n }\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-PAWJFY3S.mjs"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { __name } from './chunk-PAWJFY3S.mjs';
|
|
2
|
+
|
|
3
|
+
// src/emitters/Emitter.ts
|
|
4
|
+
var _Emitter = class _Emitter {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Emit an event. No-op if no listeners are registered.
|
|
10
|
+
*/
|
|
11
|
+
emit(event, data) {
|
|
12
|
+
const handlers = this.listeners.get(event);
|
|
13
|
+
if (!handlers || handlers.size === 0) return;
|
|
14
|
+
handlers.forEach((h) => h(data));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Subscribe to an event. Returns an unsubscribe function.
|
|
18
|
+
*/
|
|
19
|
+
on(event, handler) {
|
|
20
|
+
if (!this.listeners.has(event)) {
|
|
21
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
22
|
+
}
|
|
23
|
+
this.listeners.get(event).add(handler);
|
|
24
|
+
return () => this.listeners.get(event)?.delete(handler);
|
|
25
|
+
}
|
|
26
|
+
/** True when at least one listener is registered for this event (hot-path guard) */
|
|
27
|
+
hasListeners(event) {
|
|
28
|
+
return (this.listeners.get(event)?.size ?? 0) > 0;
|
|
29
|
+
}
|
|
30
|
+
/** Remove all listeners (useful for testing / cleanup) */
|
|
31
|
+
clear() {
|
|
32
|
+
this.listeners.clear();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
__name(_Emitter, "Emitter");
|
|
36
|
+
var Emitter = _Emitter;
|
|
37
|
+
|
|
38
|
+
// src/emitters/audioEmitter.ts
|
|
39
|
+
var _emitter = new Emitter();
|
|
40
|
+
var BUFFER_SIZE = 200;
|
|
41
|
+
var _buffer = [];
|
|
42
|
+
var emitAudioEvent = /* @__PURE__ */ __name((event) => {
|
|
43
|
+
if (_buffer.length >= BUFFER_SIZE) _buffer.shift();
|
|
44
|
+
_buffer.push(event);
|
|
45
|
+
_emitter.emit("event", event);
|
|
46
|
+
}, "emitAudioEvent");
|
|
47
|
+
var subscribeAudioEvents = /* @__PURE__ */ __name((fn) => {
|
|
48
|
+
for (const e of _buffer) fn(e);
|
|
49
|
+
return _emitter.on("event", fn);
|
|
50
|
+
}, "subscribeAudioEvents");
|
|
51
|
+
var clearAudioEventBuffer = /* @__PURE__ */ __name(() => {
|
|
52
|
+
_buffer.length = 0;
|
|
53
|
+
}, "clearAudioEventBuffer");
|
|
54
|
+
var hasAudioListeners = /* @__PURE__ */ __name(() => _emitter.hasListeners("event"), "hasAudioListeners");
|
|
55
|
+
|
|
56
|
+
// src/emitters/customEmitter.ts
|
|
57
|
+
var _emitter2 = new Emitter();
|
|
58
|
+
var emitDebugEvent = /* @__PURE__ */ __name((event) => _emitter2.emit("event", event), "emitDebugEvent");
|
|
59
|
+
var subscribeCustomEvents = /* @__PURE__ */ __name((fn) => _emitter2.on("event", fn), "subscribeCustomEvents");
|
|
60
|
+
var hasCustomListeners = /* @__PURE__ */ __name(() => _emitter2.hasListeners("event"), "hasCustomListeners");
|
|
61
|
+
|
|
62
|
+
export { Emitter, clearAudioEventBuffer, emitAudioEvent, emitDebugEvent, hasAudioListeners, hasCustomListeners, subscribeAudioEvents, subscribeCustomEvents };
|
|
63
|
+
//# sourceMappingURL=chunk-YQE3KTBG.mjs.map
|
|
64
|
+
//# sourceMappingURL=chunk-YQE3KTBG.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/emitters/Emitter.ts","../src/emitters/audioEmitter.ts","../src/emitters/customEmitter.ts"],"names":["_emitter"],"mappings":";;;AAiBO,IAAM,QAAA,GAAN,MAAM,QAAA,CAAkC;AAAA,EAAxC,WAAA,GAAA;AACL,IAAA,IAAA,CAAQ,SAAA,uBAAgB,GAAA,EAAiD;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAKzE,IAAA,CAA8B,OAAU,IAAA,EAAwB;AAC9D,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AACzC,IAAA,IAAI,CAAC,QAAA,IAAY,QAAA,CAAS,IAAA,KAAS,CAAA,EAAG;AACtC,IAAA,QAAA,CAAS,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAC,CAAA;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,EAAA,CAA4B,OAAU,OAAA,EAAiD;AACrF,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG;AAC9B,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAA,kBAAO,IAAI,KAAK,CAAA;AAAA,IACrC;AAEA,IAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,CAAG,IAAI,OAAc,CAAA;AAC7C,IAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,IAAI,KAAK,CAAA,EAAG,OAAO,OAAc,CAAA;AAAA,EAC/D;AAAA;AAAA,EAGA,aAAsC,KAAA,EAAmB;AACvD,IAAA,OAAA,CAAQ,KAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG,QAAQ,CAAA,IAAK,CAAA;AAAA,EAClD;AAAA;AAAA,EAGA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,UAAU,KAAA,EAAM;AAAA,EACvB;AACF,CAAA;AAjC+C,MAAA,CAAA,QAAA,EAAA,SAAA,CAAA;AAAxC,IAAM,OAAA,GAAN;;;AC2BP,IAAM,QAAA,GAAW,IAAI,OAAA,EAAqB;AAG1C,IAAM,WAAA,GAAc,GAAA;AACpB,IAAM,UAA6B,EAAC;AAG7B,IAAM,cAAA,2BAAkB,KAAA,KAAiC;AAE9D,EAAA,IAAI,OAAA,CAAQ,MAAA,IAAU,WAAA,EAAa,OAAA,CAAQ,KAAA,EAAM;AACjD,EAAA,OAAA,CAAQ,KAAK,KAAK,CAAA;AAClB,EAAA,QAAA,CAAS,IAAA,CAAK,SAAS,KAAK,CAAA;AAC9B,CAAA,EAL8B,gBAAA;AAYvB,IAAM,oBAAA,2BAAwB,EAAA,KAAmD;AAEtF,EAAA,KAAA,MAAW,CAAA,IAAK,OAAA,EAAS,EAAA,CAAG,CAAC,CAAA;AAC7B,EAAA,OAAO,QAAA,CAAS,EAAA,CAAG,OAAA,EAAS,EAAE,CAAA;AAChC,CAAA,EAJoC,sBAAA;AAO7B,IAAM,wCAAwB,MAAA,CAAA,MAAY;AAAE,EAAA,OAAA,CAAQ,MAAA,GAAS,CAAA;AAAG,CAAA,EAAlC,uBAAA;AAG9B,IAAM,iBAAA,mBAAoB,MAAA,CAAA,MAAe,QAAA,CAAS,YAAA,CAAa,OAAO,CAAA,EAA5C,mBAAA;;;AC1CjC,IAAMA,SAAAA,GAAW,IAAI,OAAA,EAAsB;AAGpC,IAAM,iCAAiB,MAAA,CAAA,CAAC,KAAA,KAC7BA,UAAS,IAAA,CAAK,OAAA,EAAS,KAAK,CAAA,EADA,gBAAA;AAIvB,IAAM,wCAAwB,MAAA,CAAA,CAAC,EAAA,KACpCA,UAAS,EAAA,CAAG,OAAA,EAAS,EAAE,CAAA,EADY,uBAAA;AAG9B,IAAM,kBAAA,mBAAqB,MAAA,CAAA,MAAeA,SAAAA,CAAS,YAAA,CAAa,OAAO,CAAA,EAA5C,oBAAA","file":"chunk-YQE3KTBG.mjs","sourcesContent":["/**\n * Generic typed event emitter\n *\n * Zero-cost: emit() is a no-op when no listeners are registered for an event.\n * SSR-safe: no browser APIs used.\n * No dependencies.\n *\n * Usage:\n * type MyEvents = { 'user:login': { id: string }; 'page:view': { path: string } };\n * export const myEmitter = new Emitter<MyEvents>();\n * myEmitter.on('user:login', ({ id }) => console.log(id));\n * myEmitter.emit('user:login', { id: '123' });\n */\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EventMap = Record<string, any>;\n\nexport class Emitter<TEvents extends EventMap> {\n private listeners = new Map<keyof TEvents, Set<(data: unknown) => void>>();\n\n /**\n * Emit an event. No-op if no listeners are registered.\n */\n emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void {\n const handlers = this.listeners.get(event);\n if (!handlers || handlers.size === 0) return;\n handlers.forEach((h) => h(data));\n }\n\n /**\n * Subscribe to an event. Returns an unsubscribe function.\n */\n on<K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.listeners.get(event)!.add(handler as any);\n return () => this.listeners.get(event)?.delete(handler as any);\n }\n\n /** True when at least one listener is registered for this event (hot-path guard) */\n hasListeners<K extends keyof TEvents>(event: K): boolean {\n return (this.listeners.get(event)?.size ?? 0) > 0;\n }\n\n /** Remove all listeners (useful for testing / cleanup) */\n clear(): void {\n this.listeners.clear();\n }\n}\n","/**\n * Audio/Media Engine Event Emitter\n *\n * Universal zero-cost pub/sub for audio/media player debug events.\n * No React dependency — pure TypeScript module.\n *\n * Wire into your audio engine:\n * import { emitAudioEvent } from '@org/debuger/emitters';\n * emitAudioEvent({ kind: 'play', trackId: 'abc', ts: Date.now(), msg: 'started' });\n */\n\nimport { Emitter } from './Emitter';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type AudioEventKind =\n | 'play'\n | 'pause'\n | 'seek'\n | 'ended'\n | 'sync' // periodic sync tick (RAF loop)\n | 'load'\n | 'error'\n | 'engine' // engine lifecycle: start/stop/create/destroy\n | 'custom';\n\nexport interface AudioDebugEvent {\n kind: AudioEventKind;\n ts: number;\n /** Track/player ID (short prefix) */\n trackId?: string;\n msg: string;\n /** Arbitrary metadata: positions, drift, etc. */\n data?: Record<string, unknown>;\n}\n\ntype AudioEvents = { event: AudioDebugEvent };\n\n// ============================================================================\n// Singleton emitter + ring-buffer\n// ============================================================================\n\nconst _emitter = new Emitter<AudioEvents>();\n\n/** Ring-buffer of recent events — replayed to new subscribers */\nconst BUFFER_SIZE = 200;\nconst _buffer: AudioDebugEvent[] = [];\n\n/** Emit an audio debug event. Zero-cost when no panel is open. */\nexport const emitAudioEvent = (event: AudioDebugEvent): void => {\n // Always buffer (capped) so late subscribers get recent history\n if (_buffer.length >= BUFFER_SIZE) _buffer.shift();\n _buffer.push(event);\n _emitter.emit('event', event);\n};\n\n/**\n * Subscribe to audio debug events.\n * Immediately replays buffered events to the new subscriber,\n * then delivers live events. Returns unsubscribe function.\n */\nexport const subscribeAudioEvents = (fn: (e: AudioDebugEvent) => void): (() => void) => {\n // Replay history so the panel sees events that happened before it opened\n for (const e of _buffer) fn(e);\n return _emitter.on('event', fn);\n};\n\n/** Clear the ring-buffer (called from the panel's Clear button) */\nexport const clearAudioEventBuffer = (): void => { _buffer.length = 0; };\n\n/** True when at least one listener is active (use as hot-path guard) */\nexport const hasAudioListeners = (): boolean => _emitter.hasListeners('event');\n","/**\n * Custom Debug Event Emitter\n *\n * Generic channel for consuming apps to send arbitrary debug events\n * without coupling to any specific domain.\n *\n * import { emitDebugEvent } from '@org/debuger/emitters';\n * emitDebugEvent({ channel: 'pipeline', kind: 'slot:updated', ts: Date.now(), msg: '...' });\n */\n\nimport { Emitter } from './Emitter';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface CustomDebugEvent {\n /** Logical grouping — matches registered custom tab id */\n channel: string;\n kind: string;\n ts: number;\n msg: string;\n data?: Record<string, unknown>;\n}\n\ntype CustomEvents = { event: CustomDebugEvent };\n\n// ============================================================================\n// Singleton emitter\n// ============================================================================\n\nconst _emitter = new Emitter<CustomEvents>();\n\n/** Emit a custom debug event. Zero-cost when no panel is open. */\nexport const emitDebugEvent = (event: CustomDebugEvent): void =>\n _emitter.emit('event', event);\n\n/** Subscribe to custom debug events. Returns unsubscribe function. */\nexport const subscribeCustomEvents = (fn: (e: CustomDebugEvent) => void): (() => void) =>\n _emitter.on('event', fn);\n\nexport const hasCustomListeners = (): boolean => _emitter.hasListeners('event');\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio/Media Engine Event Emitter
|
|
3
|
+
*
|
|
4
|
+
* Universal zero-cost pub/sub for audio/media player debug events.
|
|
5
|
+
* No React dependency — pure TypeScript module.
|
|
6
|
+
*
|
|
7
|
+
* Wire into your audio engine:
|
|
8
|
+
* import { emitAudioEvent } from '@org/debuger/emitters';
|
|
9
|
+
* emitAudioEvent({ kind: 'play', trackId: 'abc', ts: Date.now(), msg: 'started' });
|
|
10
|
+
*/
|
|
11
|
+
type AudioEventKind = 'play' | 'pause' | 'seek' | 'ended' | 'sync' | 'load' | 'error' | 'engine' | 'custom';
|
|
12
|
+
interface AudioDebugEvent {
|
|
13
|
+
kind: AudioEventKind;
|
|
14
|
+
ts: number;
|
|
15
|
+
/** Track/player ID (short prefix) */
|
|
16
|
+
trackId?: string;
|
|
17
|
+
msg: string;
|
|
18
|
+
/** Arbitrary metadata: positions, drift, etc. */
|
|
19
|
+
data?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
/** Emit an audio debug event. Zero-cost when no panel is open. */
|
|
22
|
+
declare const emitAudioEvent: (event: AudioDebugEvent) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Subscribe to audio debug events.
|
|
25
|
+
* Immediately replays buffered events to the new subscriber,
|
|
26
|
+
* then delivers live events. Returns unsubscribe function.
|
|
27
|
+
*/
|
|
28
|
+
declare const subscribeAudioEvents: (fn: (e: AudioDebugEvent) => void) => (() => void);
|
|
29
|
+
/** Clear the ring-buffer (called from the panel's Clear button) */
|
|
30
|
+
declare const clearAudioEventBuffer: () => void;
|
|
31
|
+
/** True when at least one listener is active (use as hot-path guard) */
|
|
32
|
+
declare const hasAudioListeners: () => boolean;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Custom Debug Event Emitter
|
|
36
|
+
*
|
|
37
|
+
* Generic channel for consuming apps to send arbitrary debug events
|
|
38
|
+
* without coupling to any specific domain.
|
|
39
|
+
*
|
|
40
|
+
* import { emitDebugEvent } from '@org/debuger/emitters';
|
|
41
|
+
* emitDebugEvent({ channel: 'pipeline', kind: 'slot:updated', ts: Date.now(), msg: '...' });
|
|
42
|
+
*/
|
|
43
|
+
interface CustomDebugEvent {
|
|
44
|
+
/** Logical grouping — matches registered custom tab id */
|
|
45
|
+
channel: string;
|
|
46
|
+
kind: string;
|
|
47
|
+
ts: number;
|
|
48
|
+
msg: string;
|
|
49
|
+
data?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
/** Emit a custom debug event. Zero-cost when no panel is open. */
|
|
52
|
+
declare const emitDebugEvent: (event: CustomDebugEvent) => void;
|
|
53
|
+
/** Subscribe to custom debug events. Returns unsubscribe function. */
|
|
54
|
+
declare const subscribeCustomEvents: (fn: (e: CustomDebugEvent) => void) => (() => void);
|
|
55
|
+
declare const hasCustomListeners: () => boolean;
|
|
56
|
+
|
|
57
|
+
export { type AudioDebugEvent as A, type CustomDebugEvent as C, type AudioEventKind as a, subscribeCustomEvents as b, clearAudioEventBuffer as c, emitDebugEvent as d, emitAudioEvent as e, hasCustomListeners as f, hasAudioListeners as h, subscribeAudioEvents as s };
|