qdadm 1.13.0 → 1.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -4
- package/src/chain/ActiveStack.ts +79 -98
- package/src/chain/StackHydrator.ts +3 -2
- package/src/chain/index.ts +7 -1
- package/src/components/QdadmRoot.vue +52 -0
- package/src/components/edit/FormActions.vue +9 -6
- package/src/components/edit/LookupPickerDialog.vue +6 -3
- package/src/components/index.ts +6 -0
- package/src/composables/useEntityItemFormPage.ts +1 -0
- package/src/composables/useEntityItemShowPage.ts +1 -0
- package/src/composables/useFieldManager.ts +100 -3
- package/src/composables/useListPage.ts +50 -59
- package/src/composables/useListPage.utils.ts +101 -0
- package/src/composables/useNavigation.ts +26 -3
- package/src/composables/useOptionsLookup.ts +5 -1
- package/src/gen/generateManagers.test.js +27 -0
- package/src/gen/generateManagers.ts +12 -0
- package/src/hooks/HookRegistry.ts +14 -435
- package/src/i18n/I18n.ts +344 -0
- package/src/i18n/IncrementalDomainProvider.ts +153 -0
- package/src/i18n/InlineTranslationProvider.ts +4 -0
- package/src/i18n/LazyTranslationProvider.ts +102 -0
- package/src/i18n/MessagesRegistry.ts +4 -0
- package/src/i18n/Resolver.ts +4 -0
- package/src/i18n/__tests__/I18n.test.ts +169 -0
- package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
- package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
- package/src/i18n/__tests__/Resolver.test.ts +271 -0
- package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
- package/src/i18n/defaults/core.en.yml +55 -0
- package/src/i18n/defaults/core.fr.yml +55 -0
- package/src/i18n/index.ts +55 -0
- package/src/i18n/loaders/raw-modules.d.ts +15 -0
- package/src/i18n/loaders/yaml.ts +35 -0
- package/src/i18n/strategies.ts +4 -0
- package/src/i18n/types.ts +34 -0
- package/src/i18n/useI18n.ts +34 -0
- package/src/index.ts +37 -0
- package/src/kernel/EventRouter.ts +17 -300
- package/src/kernel/Kernel.i18n.ts +29 -0
- package/src/kernel/Kernel.modules.ts +6 -0
- package/src/kernel/Kernel.registries.ts +10 -2
- package/src/kernel/Kernel.routing.ts +43 -1
- package/src/kernel/Kernel.ts +43 -0
- package/src/kernel/Kernel.types.ts +52 -1
- package/src/kernel/Kernel.vue.ts +121 -15
- package/src/kernel/KernelContext.entities.ts +80 -0
- package/src/kernel/KernelContext.events.ts +57 -0
- package/src/kernel/KernelContext.i18n.ts +37 -0
- package/src/kernel/KernelContext.permissions.ts +38 -0
- package/src/kernel/KernelContext.routing.ts +280 -0
- package/src/kernel/KernelContext.ts +125 -834
- package/src/kernel/KernelContext.types.ts +173 -0
- package/src/kernel/KernelContext.zones.ts +54 -0
- package/src/kernel/SSEBridge.ts +7 -362
- package/src/kernel/SignalBus.ts +24 -148
- package/src/modules/debug/AuthCollector.ts +48 -1
- package/src/modules/debug/Collector.ts +16 -302
- package/src/modules/debug/DebugBridge.ts +10 -171
- package/src/modules/debug/DebugModule.ts +35 -5
- package/src/modules/debug/EntitiesCollector.ts +97 -1
- package/src/modules/debug/ErrorCollector.ts +2 -77
- package/src/modules/debug/I18nCollector.ts +9 -0
- package/src/modules/debug/LocalStorageAdapter.ts +3 -147
- package/src/modules/debug/RouterCollector.ts +101 -1
- package/src/modules/debug/SignalCollector.ts +2 -150
- package/src/modules/debug/ToastCollector.ts +2 -91
- package/src/modules/debug/ZonesCollector.ts +93 -1
- package/src/modules/debug/components/DebugBar.vue +19 -775
- package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
- package/src/modules/debug/components/index.ts +4 -3
- package/src/modules/debug/components/panels/AuthPanel.vue +1 -1
- package/src/modules/debug/components/panels/EntitiesPanel.vue +5 -5
- package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
- package/src/modules/debug/components/panels/RouterPanel.vue +1 -1
- package/src/modules/debug/components/panels/index.ts +10 -4
- package/src/modules/debug/index.ts +15 -0
- package/src/modules/debug/styles.scss +22 -18
- package/src/utils/index.ts +0 -3
- package/src/vite/qdadmDebugPlugin.ts +401 -0
- package/src/vite-env.d.ts +16 -0
- package/src/modules/debug/components/ObjectTree.vue +0 -123
- package/src/modules/debug/components/panels/EntriesPanel.vue +0 -100
- package/src/modules/debug/components/panels/SignalsPanel.vue +0 -188
- package/src/modules/debug/components/panels/ToastsPanel.vue +0 -45
- package/src/utils/debugInjector.ts +0 -306
package/src/kernel/SignalBus.ts
CHANGED
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SignalBus -
|
|
2
|
+
* SignalBus - qdadm wiring.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The dispatcher itself now lives in `@quazardous/qdcore` so it can be shared
|
|
5
|
+
* with qdcms. This module re-exports the generic API and adds the qdadm
|
|
6
|
+
* application-level signal-name registry (`SIGNALS`).
|
|
6
7
|
*
|
|
7
|
-
* Signal naming conventions:
|
|
8
|
+
* Signal naming conventions (qdadm flavor):
|
|
8
9
|
* - Generic CRUD: entity:created, entity:updated, entity:deleted
|
|
9
|
-
* - Entity-specific: {entityName}:created,
|
|
10
|
+
* - Entity-specific: {entityName}:created, etc. — built via `buildSignal()`
|
|
11
|
+
* - Auth: auth:login, auth:logout, auth:expired
|
|
12
|
+
* - API: api:error
|
|
10
13
|
*
|
|
11
|
-
* Wildcard subscriptions
|
|
12
|
-
* - 'entity:*' matches entity:created
|
|
13
|
-
* - '
|
|
14
|
-
* - '*:created' matches any entity creation
|
|
14
|
+
* Wildcard subscriptions are inherited from QuarKernel:
|
|
15
|
+
* - 'entity:*' matches entity:created/updated/deleted
|
|
16
|
+
* - '*:created' matches any creation
|
|
15
17
|
*/
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from '@quazardous/
|
|
19
|
+
export {
|
|
20
|
+
SignalBus,
|
|
21
|
+
createSignalBus,
|
|
22
|
+
buildSignal,
|
|
23
|
+
SIGNAL_ACTIONS,
|
|
24
|
+
} from '@quazardous/qdcore'
|
|
25
|
+
export type {
|
|
26
|
+
SignalBusOptions,
|
|
27
|
+
SignalAction,
|
|
28
|
+
EntitySignalPayload,
|
|
29
|
+
} from '@quazardous/qdcore'
|
|
23
30
|
|
|
24
31
|
/**
|
|
25
|
-
*
|
|
32
|
+
* qdadm signal-name registry.
|
|
33
|
+
* Application-level convention; kept in qdadm because qdcore is naming-agnostic.
|
|
26
34
|
*/
|
|
27
35
|
export const SIGNALS = {
|
|
28
36
|
// Generic entity lifecycle signals
|
|
@@ -37,136 +45,4 @@ export const SIGNALS = {
|
|
|
37
45
|
|
|
38
46
|
// API error signals
|
|
39
47
|
API_ERROR: 'api:error', // Emitted on any API error { status, message, url }
|
|
40
|
-
|
|
41
|
-
// Pattern for entity-specific signals
|
|
42
|
-
// Use buildSignal(entityName, action) for these
|
|
43
|
-
} as const
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Actions for entity signals
|
|
47
|
-
*/
|
|
48
|
-
export const SIGNAL_ACTIONS = {
|
|
49
|
-
CREATED: 'created',
|
|
50
|
-
UPDATED: 'updated',
|
|
51
|
-
DELETED: 'deleted',
|
|
52
48
|
} as const
|
|
53
|
-
|
|
54
|
-
export type SignalAction = (typeof SIGNAL_ACTIONS)[keyof typeof SIGNAL_ACTIONS]
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Build an entity-specific signal name
|
|
58
|
-
*/
|
|
59
|
-
export function buildSignal(entityName: string, action: SignalAction): string {
|
|
60
|
-
return `${entityName}:${action}`
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* SignalBus constructor options
|
|
65
|
-
*/
|
|
66
|
-
export interface SignalBusOptions {
|
|
67
|
-
debug?: boolean
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Entity signal payload
|
|
72
|
-
*/
|
|
73
|
-
export interface EntitySignalPayload {
|
|
74
|
-
entity: string
|
|
75
|
-
data: unknown
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* SignalBus class - wraps QuarKernel for qdadm
|
|
80
|
-
*/
|
|
81
|
-
export class SignalBus {
|
|
82
|
-
private _kernel: QuarKernel
|
|
83
|
-
|
|
84
|
-
constructor(options: SignalBusOptions = {}) {
|
|
85
|
-
this._kernel = createKernel({
|
|
86
|
-
delimiter: ':',
|
|
87
|
-
wildcard: true,
|
|
88
|
-
errorBoundary: true,
|
|
89
|
-
debug: options.debug ?? false,
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Emit a signal with payload
|
|
95
|
-
*/
|
|
96
|
-
async emit(signal: string, payload?: unknown): Promise<void> {
|
|
97
|
-
return this._kernel.emit(signal, payload)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Subscribe to a signal
|
|
102
|
-
* @returns Unbind function
|
|
103
|
-
*/
|
|
104
|
-
on(signal: string, handler: ListenerCallback, options: ListenerOptions = {}): () => void {
|
|
105
|
-
return this._kernel.on(signal, handler, options)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Unsubscribe from a signal
|
|
110
|
-
*/
|
|
111
|
-
off(signal: string, handler: ListenerCallback): void {
|
|
112
|
-
this._kernel.off(signal, handler)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Subscribe to a signal once
|
|
117
|
-
*/
|
|
118
|
-
once(signal: string, handler: ListenerCallback, options: ListenerOptions = {}): () => void {
|
|
119
|
-
return this._kernel.once(signal, handler, options)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Emit an entity lifecycle signal
|
|
124
|
-
*/
|
|
125
|
-
async emitEntity(entityName: string, action: SignalAction, data: unknown): Promise<void> {
|
|
126
|
-
const signal = buildSignal('entity', action)
|
|
127
|
-
await this.emit(signal, { entity: entityName, data })
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Get listener count for a signal
|
|
132
|
-
*/
|
|
133
|
-
listenerCount(signal?: string): number {
|
|
134
|
-
return (this._kernel as unknown as { listenerCount: (s?: string) => number }).listenerCount(signal)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Get all registered signal names
|
|
139
|
-
*/
|
|
140
|
-
signalNames(): string[] {
|
|
141
|
-
return (this._kernel as unknown as { eventNames: () => string[] }).eventNames()
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Remove all listeners
|
|
146
|
-
*/
|
|
147
|
-
offAll(signal?: string): void {
|
|
148
|
-
;(this._kernel as unknown as { offAll: (s?: string) => void }).offAll(signal)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Enable/disable debug mode
|
|
153
|
-
*/
|
|
154
|
-
debug(enabled: boolean): void {
|
|
155
|
-
this._kernel.debug(enabled)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Get the underlying QuarKernel instance
|
|
160
|
-
* Used for sharing the kernel with HookRegistry
|
|
161
|
-
*/
|
|
162
|
-
getKernel(): QuarKernel {
|
|
163
|
-
return this._kernel
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Factory function to create a SignalBus instance
|
|
169
|
-
*/
|
|
170
|
-
export function createSignalBus(options: SignalBusOptions = {}): SignalBus {
|
|
171
|
-
return new SignalBus(options)
|
|
172
|
-
}
|
|
@@ -10,7 +10,14 @@
|
|
|
10
10
|
* Only shows content when auth is configured in the application.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
Collector,
|
|
15
|
+
type CollectorContext,
|
|
16
|
+
type CollectorEntry,
|
|
17
|
+
type CollectorManifest,
|
|
18
|
+
type CollectorOptions,
|
|
19
|
+
type CollectorSnapshot,
|
|
20
|
+
} from './Collector'
|
|
14
21
|
import type { ISessionAuthAdapter, AuthUser } from '../../auth/SessionAuthAdapter'
|
|
15
22
|
import type { SecurityChecker } from '../../entity/auth/SecurityChecker'
|
|
16
23
|
|
|
@@ -451,4 +458,44 @@ export class AuthCollector extends Collector<AuthEntry> {
|
|
|
451
458
|
return null
|
|
452
459
|
}
|
|
453
460
|
}
|
|
461
|
+
|
|
462
|
+
override describe(): CollectorManifest {
|
|
463
|
+
return {
|
|
464
|
+
name: this.name,
|
|
465
|
+
records: false,
|
|
466
|
+
summary:
|
|
467
|
+
'Surfaces current authentication state: user, roles, permissions, hierarchy, token claims, adapter capabilities, plus a TTL-bounded log of recent auth events.',
|
|
468
|
+
entryShape: {
|
|
469
|
+
type: 'string (user|impersonated|token|user-permissions|hierarchy|role-permissions|permissions|adapter|status|error)',
|
|
470
|
+
label: 'string?',
|
|
471
|
+
message: 'string?',
|
|
472
|
+
data: 'json',
|
|
473
|
+
},
|
|
474
|
+
stateShape: {
|
|
475
|
+
recentEvents: 'AuthEvent[] (TTL-bounded)',
|
|
476
|
+
lastEvent: 'string?',
|
|
477
|
+
},
|
|
478
|
+
actions: this._builtinActionManifests(),
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
override snapshot(): CollectorSnapshot {
|
|
483
|
+
const entries = this.getEntries()
|
|
484
|
+
return {
|
|
485
|
+
name: this.name,
|
|
486
|
+
entries,
|
|
487
|
+
count: entries.length,
|
|
488
|
+
unseen: this.getBadge(),
|
|
489
|
+
state: {
|
|
490
|
+
recentEvents: this._recentEvents.map((e) => ({
|
|
491
|
+
type: e.type,
|
|
492
|
+
timestamp: e.timestamp.toISOString(),
|
|
493
|
+
id: e.id,
|
|
494
|
+
seen: e.seen,
|
|
495
|
+
data: e.data,
|
|
496
|
+
})),
|
|
497
|
+
lastEvent: this.getLastEvent(),
|
|
498
|
+
},
|
|
499
|
+
}
|
|
500
|
+
}
|
|
454
501
|
}
|
|
@@ -1,306 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Collector
|
|
2
|
+
* Collector — qdadm re-export.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Subclasses implement specific collection strategies by overriding
|
|
8
|
-
* the install() and uninstall() methods.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* class SignalCollector extends Collector {
|
|
12
|
-
* static override collectorName = 'signals'
|
|
13
|
-
*
|
|
14
|
-
* protected override _doInstall(ctx: CollectorContext) {
|
|
15
|
-
* this._unbind = ctx.signals.on('*', (event) => {
|
|
16
|
-
* this.record({ type: event.name, data: event.data })
|
|
17
|
-
* })
|
|
18
|
-
* }
|
|
19
|
-
*
|
|
20
|
-
* protected override _doUninstall() {
|
|
21
|
-
* if (this._unbind) this._unbind()
|
|
22
|
-
* }
|
|
23
|
-
* }
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import type { SignalBus } from '../../kernel/SignalBus'
|
|
27
|
-
import type { Orchestrator } from '../../orchestrator/Orchestrator'
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Context provided to collectors during install
|
|
31
|
-
*/
|
|
32
|
-
export interface CollectorContext {
|
|
33
|
-
signals?: SignalBus | null
|
|
34
|
-
orchestrator?: Orchestrator | null
|
|
35
|
-
router?: unknown
|
|
36
|
-
kernel?: unknown
|
|
37
|
-
[key: string]: unknown
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Base entry type for collector entries
|
|
42
|
-
*/
|
|
43
|
-
export interface CollectorEntry {
|
|
44
|
-
timestamp: number
|
|
45
|
-
_isNew?: boolean
|
|
46
|
-
[key: string]: unknown
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Collector options
|
|
51
|
-
*/
|
|
52
|
-
export interface CollectorOptions {
|
|
53
|
-
maxEntries?: number
|
|
54
|
-
enabled?: boolean
|
|
55
|
-
[key: string]: unknown
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Notify callback type
|
|
60
|
-
*/
|
|
61
|
-
export type NotifyCallback = () => void
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Debug bridge interface
|
|
65
|
-
*/
|
|
66
|
-
export interface DebugBridgeInterface {
|
|
67
|
-
notify: () => void
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Base class for debug collectors
|
|
4
|
+
* The actual implementation now lives in `@quazardous/qddebug` so it can be
|
|
5
|
+
* shared with qdcms. Existing internal imports (`from './Collector'`)
|
|
6
|
+
* continue to work via this shim.
|
|
72
7
|
*/
|
|
73
|
-
export class Collector<TEntry extends CollectorEntry = CollectorEntry> {
|
|
74
|
-
/**
|
|
75
|
-
* Collector name - override in subclass
|
|
76
|
-
* Using collectorName to avoid conflict with Function.name
|
|
77
|
-
*/
|
|
78
|
-
static collectorName = 'base'
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Whether this collector actually records (vs just displays state)
|
|
82
|
-
* Override to false for collectors that only show current state (e.g., zones, auth)
|
|
83
|
-
*/
|
|
84
|
-
static records = true
|
|
85
|
-
|
|
86
|
-
options: CollectorOptions
|
|
87
|
-
maxEntries: number
|
|
88
|
-
entries: TEntry[]
|
|
89
|
-
protected _enabled: boolean
|
|
90
|
-
protected _installed: boolean
|
|
91
|
-
protected _ctx: CollectorContext | null
|
|
92
|
-
protected _seenCount: number
|
|
93
|
-
_bridge: DebugBridgeInterface | null
|
|
94
|
-
protected _notifyCallbacks: NotifyCallback[]
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Create a new collector
|
|
98
|
-
* @param options - Collector options
|
|
99
|
-
*/
|
|
100
|
-
constructor(options: CollectorOptions = {}) {
|
|
101
|
-
this.options = options
|
|
102
|
-
this.maxEntries = options.maxEntries ?? 100
|
|
103
|
-
this.entries = []
|
|
104
|
-
this._enabled = options.enabled ?? true
|
|
105
|
-
this._installed = false
|
|
106
|
-
this._ctx = null
|
|
107
|
-
this._seenCount = 0 // Number of entries that have been "seen"
|
|
108
|
-
this._bridge = null // Set by DebugBridge when added
|
|
109
|
-
this._notifyCallbacks = [] // Direct notification subscribers
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Check if this collector type records events (vs showing state)
|
|
114
|
-
*/
|
|
115
|
-
get records(): boolean {
|
|
116
|
-
return (this.constructor as typeof Collector).records
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Check if collector is enabled
|
|
121
|
-
*/
|
|
122
|
-
get enabled(): boolean {
|
|
123
|
-
return this._enabled
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Set enabled state - installs/uninstalls as needed
|
|
128
|
-
*/
|
|
129
|
-
set enabled(value: boolean) {
|
|
130
|
-
if (this._enabled === value) return
|
|
131
|
-
this._enabled = value
|
|
132
|
-
if (this._ctx) {
|
|
133
|
-
if (value) {
|
|
134
|
-
this.install(this._ctx)
|
|
135
|
-
} else {
|
|
136
|
-
this.uninstall()
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Toggle enabled state
|
|
143
|
-
* @returns New enabled state
|
|
144
|
-
*/
|
|
145
|
-
toggle(): boolean {
|
|
146
|
-
this.enabled = !this._enabled
|
|
147
|
-
return this._enabled
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Get collector name from static property
|
|
152
|
-
*/
|
|
153
|
-
get name(): string {
|
|
154
|
-
return (this.constructor as typeof Collector).collectorName
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Record an entry in the ring buffer
|
|
159
|
-
* Automatically adds timestamp and enforces max entries
|
|
160
|
-
* @param entry - Entry data to record
|
|
161
|
-
*/
|
|
162
|
-
record(entry: Omit<TEntry, 'timestamp' | '_isNew'>): void {
|
|
163
|
-
const fullEntry = { ...entry, timestamp: Date.now(), _isNew: true } as TEntry
|
|
164
|
-
this.entries.push(fullEntry)
|
|
165
|
-
if (this.entries.length > this.maxEntries) {
|
|
166
|
-
this.entries.shift()
|
|
167
|
-
// Adjust seenCount when oldest entry is removed
|
|
168
|
-
if (this._seenCount > 0) {
|
|
169
|
-
this._seenCount--
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// Notify bridge and direct subscribers
|
|
173
|
-
this.notifyChange()
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Notify bridge that state has changed (for non-recording collectors)
|
|
178
|
-
* Call this when collector state changes that should trigger UI update
|
|
179
|
-
*/
|
|
180
|
-
notifyChange(): void {
|
|
181
|
-
this._bridge?.notify()
|
|
182
|
-
// Call direct subscribers
|
|
183
|
-
for (const cb of this._notifyCallbacks) {
|
|
184
|
-
try {
|
|
185
|
-
cb()
|
|
186
|
-
} catch (e) {
|
|
187
|
-
console.warn('[Collector] Notify callback error:', e)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Subscribe to change notifications
|
|
194
|
-
* @param callback - Called when collector state changes
|
|
195
|
-
* @returns Unsubscribe function
|
|
196
|
-
*/
|
|
197
|
-
onNotify(callback: NotifyCallback): () => void {
|
|
198
|
-
this._notifyCallbacks.push(callback)
|
|
199
|
-
return () => {
|
|
200
|
-
const idx = this._notifyCallbacks.indexOf(callback)
|
|
201
|
-
if (idx >= 0) this._notifyCallbacks.splice(idx, 1)
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Get unseen count (new entries since last markAsSeen)
|
|
207
|
-
* @returns Number of unseen entries
|
|
208
|
-
*/
|
|
209
|
-
getUnseenCount(): number {
|
|
210
|
-
return Math.max(0, this.entries.length - this._seenCount)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Get total count
|
|
215
|
-
* @returns Total number of entries
|
|
216
|
-
*/
|
|
217
|
-
getTotalCount(): number {
|
|
218
|
-
return this.entries.length
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Get badge count for UI display
|
|
223
|
-
* @param countAll - If true, return total count; otherwise unseen count
|
|
224
|
-
* @returns Number of entries
|
|
225
|
-
*/
|
|
226
|
-
getBadge(countAll = false): number {
|
|
227
|
-
return countAll ? this.getTotalCount() : this.getUnseenCount()
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Mark all current entries as seen
|
|
232
|
-
* Removes _isNew flag and updates seenCount
|
|
233
|
-
*/
|
|
234
|
-
markAsSeen(): void {
|
|
235
|
-
this._seenCount = this.entries.length
|
|
236
|
-
// Remove _isNew flag from all entries
|
|
237
|
-
for (const entry of this.entries) {
|
|
238
|
-
delete entry._isNew
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Clear all entries
|
|
244
|
-
*/
|
|
245
|
-
clear(): void {
|
|
246
|
-
this.entries = []
|
|
247
|
-
this._seenCount = 0
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Get all entries
|
|
252
|
-
* Returns a shallow copy to trigger Vue reactivity when used in computed
|
|
253
|
-
* @returns All recorded entries
|
|
254
|
-
*/
|
|
255
|
-
getEntries(): TEntry[] {
|
|
256
|
-
return [...this.entries]
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Get latest entries
|
|
261
|
-
* @param count - Number of entries to return
|
|
262
|
-
* @returns Latest entries
|
|
263
|
-
*/
|
|
264
|
-
getLatest(count: number): TEntry[] {
|
|
265
|
-
return this.entries.slice(-count)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Install collector - subscribe to signals, events, etc.
|
|
270
|
-
* Stores context for later use. Override _doInstall in subclass.
|
|
271
|
-
* @param ctx - Context object with signals, router, etc.
|
|
272
|
-
*/
|
|
273
|
-
install(ctx: CollectorContext): void {
|
|
274
|
-
this._ctx = ctx
|
|
275
|
-
this._installed = true
|
|
276
|
-
if (this._enabled) {
|
|
277
|
-
this._doInstall(ctx)
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Uninstall collector - cleanup subscriptions
|
|
283
|
-
* Override _doUninstall in subclass to cleanup resources
|
|
284
|
-
*/
|
|
285
|
-
uninstall(): void {
|
|
286
|
-
this._doUninstall()
|
|
287
|
-
this._installed = false
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Internal install - override in subclass
|
|
292
|
-
* @param ctx - Context object
|
|
293
|
-
* @protected
|
|
294
|
-
*/
|
|
295
|
-
protected _doInstall(_ctx: CollectorContext): void {
|
|
296
|
-
// Override in subclass
|
|
297
|
-
}
|
|
298
8
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
9
|
+
export {
|
|
10
|
+
Collector,
|
|
11
|
+
type CollectorEntry,
|
|
12
|
+
type CollectorContext,
|
|
13
|
+
type CollectorOptions,
|
|
14
|
+
type CollectorManifest,
|
|
15
|
+
type CollectorSnapshot,
|
|
16
|
+
type CollectorAction,
|
|
17
|
+
type CollectorActionManifest,
|
|
18
|
+
type DebugBridgeInterface,
|
|
19
|
+
type NotifyCallback,
|
|
20
|
+
} from '@quazardous/qddebug'
|