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
|
@@ -1,175 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DebugBridge
|
|
2
|
+
* DebugBridge — qdadm re-export.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* import { createDebugBridge, SignalCollector } from '@qdadm/core/debug'
|
|
9
|
-
*
|
|
10
|
-
* const debug = createDebugBridge()
|
|
11
|
-
* debug.addCollector(new SignalCollector())
|
|
12
|
-
* debug.install(ctx)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { ref, shallowReactive, type Ref, type ShallowReactive } from 'vue'
|
|
16
|
-
import type { Collector, CollectorContext } from './Collector'
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Debug bridge options
|
|
4
|
+
* The actual implementation now lives in `@quazardous/qddebug` so it can be
|
|
5
|
+
* shared with qdcms.
|
|
20
6
|
*/
|
|
21
|
-
export interface DebugBridgeOptions {
|
|
22
|
-
enabled?: boolean
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Central aggregator for debug collectors
|
|
27
|
-
*/
|
|
28
|
-
export class DebugBridge {
|
|
29
|
-
options: DebugBridgeOptions
|
|
30
|
-
enabled: Ref<boolean>
|
|
31
|
-
collectors: ShallowReactive<Map<string, Collector>>
|
|
32
|
-
tick: Ref<number>
|
|
33
|
-
private _installed: boolean = false
|
|
34
|
-
private _ctx: CollectorContext | null = null
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Create a new DebugBridge
|
|
38
|
-
* @param options - Bridge options
|
|
39
|
-
*/
|
|
40
|
-
constructor(options: DebugBridgeOptions = {}) {
|
|
41
|
-
this.options = options
|
|
42
|
-
this.enabled = ref(options.enabled ?? false)
|
|
43
|
-
// Use shallowReactive to avoid deep reactivity on collector instances
|
|
44
|
-
this.collectors = shallowReactive(new Map())
|
|
45
|
-
// Reactive tick for UI updates - collectors increment this when they have new data
|
|
46
|
-
this.tick = ref(0)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Notify that a collector has new data
|
|
51
|
-
* Called by collectors when they record new entries or state changes
|
|
52
|
-
*/
|
|
53
|
-
notify(): void {
|
|
54
|
-
// Defensive check - ensure tick is still a ref
|
|
55
|
-
if (this.tick && typeof this.tick === 'object' && 'value' in this.tick) {
|
|
56
|
-
this.tick.value++
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
7
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// Set bridge reference for reactive notifications
|
|
68
|
-
collector._bridge = this
|
|
69
|
-
this.collectors.set(name, collector)
|
|
70
|
-
if (this._installed && this.enabled.value && this._ctx) {
|
|
71
|
-
collector.install(this._ctx)
|
|
72
|
-
}
|
|
73
|
-
return this
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Get a collector by name
|
|
78
|
-
* @param name - Collector name
|
|
79
|
-
* @returns The collector or undefined
|
|
80
|
-
*/
|
|
81
|
-
getCollector(name: string): Collector | undefined {
|
|
82
|
-
return this.collectors.get(name)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Get all collectors
|
|
87
|
-
* @returns All collectors
|
|
88
|
-
*/
|
|
89
|
-
getAllCollectors(): Map<string, Collector> {
|
|
90
|
-
return this.collectors
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Install all collectors
|
|
95
|
-
* @param ctx - Context object with signals, router, etc.
|
|
96
|
-
* @returns this for chaining
|
|
97
|
-
*/
|
|
98
|
-
install(ctx: CollectorContext): this {
|
|
99
|
-
this._ctx = ctx
|
|
100
|
-
this._installed = true
|
|
101
|
-
if (this.enabled.value) {
|
|
102
|
-
for (const collector of this.collectors.values()) {
|
|
103
|
-
collector.install(ctx)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// Initial notify after all collectors are installed
|
|
107
|
-
// Use nextTick to ensure Vue reactivity is ready
|
|
108
|
-
setTimeout(() => this.notify(), 0)
|
|
109
|
-
return this
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Uninstall all collectors and cleanup
|
|
114
|
-
*/
|
|
115
|
-
uninstall(): void {
|
|
116
|
-
for (const collector of this.collectors.values()) {
|
|
117
|
-
collector.uninstall()
|
|
118
|
-
}
|
|
119
|
-
this.collectors.clear()
|
|
120
|
-
this._installed = false
|
|
121
|
-
this._ctx = null
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Toggle enabled state
|
|
126
|
-
* When enabled, installs all collectors; when disabled, uninstalls them
|
|
127
|
-
* @returns New enabled state
|
|
128
|
-
*/
|
|
129
|
-
toggle(): boolean {
|
|
130
|
-
this.enabled.value = !this.enabled.value
|
|
131
|
-
if (this._installed && this._ctx) {
|
|
132
|
-
if (this.enabled.value) {
|
|
133
|
-
for (const collector of this.collectors.values()) {
|
|
134
|
-
collector.install(this._ctx)
|
|
135
|
-
}
|
|
136
|
-
} else {
|
|
137
|
-
for (const collector of this.collectors.values()) {
|
|
138
|
-
collector.uninstall()
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return this.enabled.value
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Clear all collectors' entries
|
|
147
|
-
*/
|
|
148
|
-
clearAll(): void {
|
|
149
|
-
for (const collector of this.collectors.values()) {
|
|
150
|
-
collector.clear()
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Get total badge count across all collectors
|
|
156
|
-
* @param countAll - If true, count all entries; otherwise unseen only
|
|
157
|
-
* @returns Total entry count
|
|
158
|
-
*/
|
|
159
|
-
getTotalBadge(countAll = false): number {
|
|
160
|
-
let total = 0
|
|
161
|
-
for (const collector of this.collectors.values()) {
|
|
162
|
-
total += collector.getBadge(countAll)
|
|
163
|
-
}
|
|
164
|
-
return total
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Factory function to create a DebugBridge
|
|
170
|
-
* @param options - Bridge options
|
|
171
|
-
* @returns New DebugBridge instance
|
|
172
|
-
*/
|
|
173
|
-
export function createDebugBridge(options: DebugBridgeOptions = {}): DebugBridge {
|
|
174
|
-
return new DebugBridge(options)
|
|
175
|
-
}
|
|
8
|
+
export {
|
|
9
|
+
DebugBridge,
|
|
10
|
+
createDebugBridge,
|
|
11
|
+
type DebugBridgeOptions,
|
|
12
|
+
type BridgeManifest,
|
|
13
|
+
type BridgeSnapshot,
|
|
14
|
+
} from '@quazardous/qddebug'
|
|
@@ -21,13 +21,19 @@ import { ZonesCollector } from './ZonesCollector'
|
|
|
21
21
|
import { AuthCollector } from './AuthCollector'
|
|
22
22
|
import { EntitiesCollector } from './EntitiesCollector'
|
|
23
23
|
import { RouterCollector } from './RouterCollector'
|
|
24
|
+
import { I18nCollector } from './I18nCollector'
|
|
24
25
|
import DebugBar from './components/DebugBar.vue'
|
|
25
26
|
import type { KernelContext } from '../../kernel/KernelContext'
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Symbol for debug bridge injection key
|
|
29
30
|
*/
|
|
30
|
-
|
|
31
|
+
// Symbol.for() instead of Symbol() — the latter mints a fresh
|
|
32
|
+
// instance each time the module evaluates, and Vite HMR can fragment
|
|
33
|
+
// a single source module into multiple live instances per importer.
|
|
34
|
+
// Provider and consumer would then hold DIFFERENT symbols and the
|
|
35
|
+
// inject would fall back to its default (null), defeating the bridge.
|
|
36
|
+
export const DEBUG_BRIDGE_KEY = Symbol.for('qdadm.debugBridge')
|
|
31
37
|
|
|
32
38
|
/**
|
|
33
39
|
* Debug zone name for the DebugBar component
|
|
@@ -60,7 +66,17 @@ export interface DebugModuleOptions extends ModuleOptions {
|
|
|
60
66
|
authCollector?: boolean
|
|
61
67
|
entitiesCollector?: boolean
|
|
62
68
|
routerCollector?: boolean
|
|
69
|
+
i18nCollector?: boolean
|
|
63
70
|
_kernelManaged?: boolean
|
|
71
|
+
/**
|
|
72
|
+
* Existing DebugBridge to register collectors onto. When provided,
|
|
73
|
+
* the module skips its own `createDebugBridge()` AND skips
|
|
74
|
+
* `bridge.install(ctx)` — the host shell owns install timing so
|
|
75
|
+
* collectors from multiple sources (e.g. qdcms + qdadm) end up on
|
|
76
|
+
* the same bridge with a single merged install. When omitted, the
|
|
77
|
+
* module owns its bridge fully (legacy behaviour).
|
|
78
|
+
*/
|
|
79
|
+
bridge?: DebugBridge
|
|
64
80
|
}
|
|
65
81
|
|
|
66
82
|
/**
|
|
@@ -94,8 +110,10 @@ export class DebugModule extends Module {
|
|
|
94
110
|
override async connect(ctx: KernelContext): Promise<void> {
|
|
95
111
|
this.ctx = ctx as unknown as Record<string, unknown>
|
|
96
112
|
|
|
97
|
-
//
|
|
98
|
-
|
|
113
|
+
// External bridge (host-owned) when provided — the host installs
|
|
114
|
+
// it later with a merged context. Otherwise create our own.
|
|
115
|
+
const externalBridge = this.options.bridge ?? null
|
|
116
|
+
this._bridge = externalBridge ?? createDebugBridge({
|
|
99
117
|
enabled: this.options.enabled ?? false
|
|
100
118
|
})
|
|
101
119
|
|
|
@@ -130,8 +148,16 @@ export class DebugModule extends Module {
|
|
|
130
148
|
this._bridge.addCollector(new RouterCollector(collectorOptions))
|
|
131
149
|
}
|
|
132
150
|
|
|
133
|
-
|
|
134
|
-
|
|
151
|
+
if (this.options.i18nCollector !== false) {
|
|
152
|
+
this._bridge.addCollector(new I18nCollector(collectorOptions))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Install collectors with context — only when WE own the bridge.
|
|
156
|
+
// External bridges are installed by the host later with a merged
|
|
157
|
+
// context that may include data from other frameworks (e.g. cms).
|
|
158
|
+
if (!externalBridge) {
|
|
159
|
+
this._bridge.install(ctx as unknown as Record<string, unknown>)
|
|
160
|
+
}
|
|
135
161
|
|
|
136
162
|
// Define the debug zone
|
|
137
163
|
const extCtx = ctx as KernelContext & {
|
|
@@ -165,6 +191,10 @@ export class DebugModule extends Module {
|
|
|
165
191
|
|
|
166
192
|
// Register global component for use in App.vue
|
|
167
193
|
extCtx.component('QdadmDebugBar', QdadmDebugBar)
|
|
194
|
+
|
|
195
|
+
// The HTTP debug bridge (qdadmDebugPlugin) is wired entirely from the
|
|
196
|
+
// injected client script (see transformIndexHtml in the plugin). The
|
|
197
|
+
// DebugModule itself stays browser-pure and HMR-agnostic.
|
|
168
198
|
}
|
|
169
199
|
|
|
170
200
|
/**
|
|
@@ -11,7 +11,14 @@
|
|
|
11
11
|
* Shows current state rather than historical events.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
Collector,
|
|
16
|
+
type CollectorContext,
|
|
17
|
+
type CollectorEntry,
|
|
18
|
+
type CollectorManifest,
|
|
19
|
+
type CollectorOptions,
|
|
20
|
+
type CollectorSnapshot,
|
|
21
|
+
} from './Collector'
|
|
15
22
|
import type { EntityManager } from '../../entity/EntityManager'
|
|
16
23
|
|
|
17
24
|
/**
|
|
@@ -708,6 +715,95 @@ export class EntitiesCollector extends Collector<EntityEntry> {
|
|
|
708
715
|
}
|
|
709
716
|
}
|
|
710
717
|
|
|
718
|
+
override describe(): CollectorManifest {
|
|
719
|
+
return {
|
|
720
|
+
name: this.name,
|
|
721
|
+
records: false,
|
|
722
|
+
summary:
|
|
723
|
+
'Lists every registered EntityManager with its storage, cache state, permissions, stats, fields and relations.',
|
|
724
|
+
entryShape: {
|
|
725
|
+
name: 'string',
|
|
726
|
+
system: 'boolean',
|
|
727
|
+
hasActivity: 'boolean',
|
|
728
|
+
label: 'string?',
|
|
729
|
+
labelPlural: 'string?',
|
|
730
|
+
routePrefix: 'string?',
|
|
731
|
+
idField: 'string?',
|
|
732
|
+
storage: 'StorageInfo',
|
|
733
|
+
multiStorage: 'MultiStorageInfo?',
|
|
734
|
+
cache: 'CacheInfo',
|
|
735
|
+
permissions: 'PermissionsInfo',
|
|
736
|
+
authSensitive: 'boolean',
|
|
737
|
+
warmup: 'WarmupInfo',
|
|
738
|
+
stats: 'StatsInfo',
|
|
739
|
+
fields: 'FieldsInfo',
|
|
740
|
+
relations: 'RelationsInfo',
|
|
741
|
+
},
|
|
742
|
+
stateShape: {
|
|
743
|
+
registered: 'string[] (entity names)',
|
|
744
|
+
activeEntities: 'string[] (entities with unseen activity)',
|
|
745
|
+
},
|
|
746
|
+
actions: [
|
|
747
|
+
...this._builtinActionManifests(),
|
|
748
|
+
{
|
|
749
|
+
name: 'refreshCache',
|
|
750
|
+
summary: 'Invalidate one entity\'s cache (and optionally reload).',
|
|
751
|
+
args: { entity: 'string', reload: 'boolean?' },
|
|
752
|
+
mutates: true,
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
name: 'invalidateCache',
|
|
756
|
+
summary: 'Mark one entity\'s cache invalid without reloading.',
|
|
757
|
+
args: { entity: 'string' },
|
|
758
|
+
mutates: true,
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
name: 'testFetch',
|
|
762
|
+
summary: 'Probe an entity\'s primary storage with a list({page:1,page_size:1}).',
|
|
763
|
+
args: { entity: 'string' },
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
name: 'testStorageFetch',
|
|
767
|
+
summary: 'Probe a specific named storage on an entity.',
|
|
768
|
+
args: { entity: 'string', storage: 'string' },
|
|
769
|
+
},
|
|
770
|
+
],
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
override snapshot(): CollectorSnapshot {
|
|
775
|
+
const entries = this.getEntries()
|
|
776
|
+
const registered = this._orchestrator?.getRegisteredNames() ?? []
|
|
777
|
+
return {
|
|
778
|
+
name: this.name,
|
|
779
|
+
entries,
|
|
780
|
+
count: entries.length,
|
|
781
|
+
unseen: this.getBadge(),
|
|
782
|
+
state: {
|
|
783
|
+
registered,
|
|
784
|
+
activeEntities: Array.from(this._activeEntities),
|
|
785
|
+
},
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
override async call(actionName: string, args: Record<string, unknown> = {}): Promise<unknown> {
|
|
790
|
+
if (actionName === 'refreshCache') {
|
|
791
|
+
const ok = await this.refreshCache(String(args.entity ?? ''), Boolean(args.reload))
|
|
792
|
+
return { ok }
|
|
793
|
+
}
|
|
794
|
+
if (actionName === 'invalidateCache') {
|
|
795
|
+
this.invalidateCache(String(args.entity ?? ''))
|
|
796
|
+
return { ok: true }
|
|
797
|
+
}
|
|
798
|
+
if (actionName === 'testFetch') {
|
|
799
|
+
return await this.testFetch(String(args.entity ?? ''))
|
|
800
|
+
}
|
|
801
|
+
if (actionName === 'testStorageFetch') {
|
|
802
|
+
return await this.testStorageFetch(String(args.entity ?? ''), String(args.storage ?? ''))
|
|
803
|
+
}
|
|
804
|
+
return super.call(actionName, args)
|
|
805
|
+
}
|
|
806
|
+
|
|
711
807
|
/**
|
|
712
808
|
* Test fetch data from a specific storage of an entity
|
|
713
809
|
*/
|
|
@@ -1,79 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ErrorCollector
|
|
3
|
-
*
|
|
4
|
-
* This collector listens to global window error events and unhandled promise
|
|
5
|
-
* rejections, recording them for display in the debug panel.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* const collector = new ErrorCollector()
|
|
9
|
-
* collector.install(ctx)
|
|
10
|
-
* // Errors are now automatically recorded
|
|
11
|
-
* // Later...
|
|
12
|
-
* collector.uninstall()
|
|
2
|
+
* ErrorCollector — qdadm re-export from @quazardous/qddebug.
|
|
13
3
|
*/
|
|
14
|
-
|
|
15
|
-
import { Collector, type CollectorContext, type CollectorEntry } from './Collector'
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Error entry type
|
|
19
|
-
*/
|
|
20
|
-
export interface ErrorEntry extends CollectorEntry {
|
|
21
|
-
message?: string
|
|
22
|
-
filename?: string
|
|
23
|
-
lineno?: number
|
|
24
|
-
colno?: number
|
|
25
|
-
error?: string
|
|
26
|
-
reason?: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Collector for JavaScript errors and unhandled promise rejections
|
|
31
|
-
*/
|
|
32
|
-
export class ErrorCollector extends Collector<ErrorEntry> {
|
|
33
|
-
/**
|
|
34
|
-
* Collector name identifier
|
|
35
|
-
*/
|
|
36
|
-
static override collectorName = 'errors'
|
|
37
|
-
|
|
38
|
-
private _handler: ((event: ErrorEvent) => void) | null = null
|
|
39
|
-
private _rejectionHandler: ((event: PromiseRejectionEvent) => void) | null = null
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Internal install - subscribe to error and unhandledrejection events
|
|
43
|
-
* @protected
|
|
44
|
-
*/
|
|
45
|
-
protected override _doInstall(_ctx: CollectorContext): void {
|
|
46
|
-
this._handler = (event: ErrorEvent) => {
|
|
47
|
-
this.record({
|
|
48
|
-
message: event.message,
|
|
49
|
-
filename: event.filename,
|
|
50
|
-
lineno: event.lineno,
|
|
51
|
-
colno: event.colno,
|
|
52
|
-
error: event.error?.stack
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
this._rejectionHandler = (event: PromiseRejectionEvent) => {
|
|
56
|
-
this.record({
|
|
57
|
-
message: 'Unhandled Promise Rejection',
|
|
58
|
-
reason: String(event.reason)
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
window.addEventListener('error', this._handler)
|
|
62
|
-
window.addEventListener('unhandledrejection', this._rejectionHandler)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Internal uninstall - remove event listeners
|
|
67
|
-
* @protected
|
|
68
|
-
*/
|
|
69
|
-
protected override _doUninstall(): void {
|
|
70
|
-
if (this._handler) {
|
|
71
|
-
window.removeEventListener('error', this._handler)
|
|
72
|
-
this._handler = null
|
|
73
|
-
}
|
|
74
|
-
if (this._rejectionHandler) {
|
|
75
|
-
window.removeEventListener('unhandledrejection', this._rejectionHandler)
|
|
76
|
-
this._rejectionHandler = null
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
4
|
+
export { ErrorCollector, type ErrorEntry } from '@quazardous/qddebug'
|
|
@@ -1,151 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LocalStorageAdapter
|
|
2
|
+
* LocalStorageAdapter — qdadm re-export.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Allows debug settings to survive page refreshes.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* import { createDebugBridge, LocalStorageAdapter } from '@qdadm/core/debug'
|
|
9
|
-
*
|
|
10
|
-
* const debug = createDebugBridge()
|
|
11
|
-
* const storage = new LocalStorageAdapter('qdadm-debug')
|
|
12
|
-
* storage.attach(debug) // Auto-saves on change, restores on load
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { DebugBridge } from './DebugBridge'
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* LocalStorage adapter for debug settings persistence
|
|
4
|
+
* The actual implementation now lives in `@quazardous/qddebug`.
|
|
19
5
|
*/
|
|
20
|
-
export class LocalStorageAdapter {
|
|
21
|
-
readonly key: string
|
|
22
|
-
private _bridge: DebugBridge | null = null
|
|
23
|
-
private _unwatch: (() => void) | null = null
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Create a new LocalStorageAdapter
|
|
27
|
-
* @param key - localStorage key prefix
|
|
28
|
-
*/
|
|
29
|
-
constructor(key = 'qdadm-debug') {
|
|
30
|
-
this.key = key
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Get the full key for a setting
|
|
35
|
-
* @param name - Setting name
|
|
36
|
-
* @returns Full localStorage key
|
|
37
|
-
*/
|
|
38
|
-
private _getKey(name: string): string {
|
|
39
|
-
return `${this.key}:${name}`
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Get a value from localStorage
|
|
44
|
-
* @param name - Setting name
|
|
45
|
-
* @param defaultValue - Default if not found
|
|
46
|
-
* @returns The value
|
|
47
|
-
*/
|
|
48
|
-
get<T>(name: string, defaultValue: T | null = null): T | null {
|
|
49
|
-
try {
|
|
50
|
-
const stored = localStorage.getItem(this._getKey(name))
|
|
51
|
-
return stored !== null ? JSON.parse(stored) : defaultValue
|
|
52
|
-
} catch {
|
|
53
|
-
return defaultValue
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Set a value in localStorage
|
|
59
|
-
* @param name - Setting name
|
|
60
|
-
* @param value - Value to store
|
|
61
|
-
*/
|
|
62
|
-
set(name: string, value: unknown): void {
|
|
63
|
-
try {
|
|
64
|
-
localStorage.setItem(this._getKey(name), JSON.stringify(value))
|
|
65
|
-
} catch {
|
|
66
|
-
// localStorage might be full or disabled
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Remove a value from localStorage
|
|
72
|
-
* @param name - Setting name
|
|
73
|
-
*/
|
|
74
|
-
remove(name: string): void {
|
|
75
|
-
localStorage.removeItem(this._getKey(name))
|
|
76
|
-
}
|
|
77
6
|
|
|
78
|
-
|
|
79
|
-
* Clear all debug settings
|
|
80
|
-
*/
|
|
81
|
-
clear(): void {
|
|
82
|
-
const prefix = this.key + ':'
|
|
83
|
-
const keys: string[] = []
|
|
84
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
85
|
-
const key = localStorage.key(i)
|
|
86
|
-
if (key && key.startsWith(prefix)) {
|
|
87
|
-
keys.push(key)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
keys.forEach(key => localStorage.removeItem(key))
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Attach to a DebugBridge and sync state
|
|
95
|
-
* Restores saved state and watches for changes
|
|
96
|
-
* @param bridge - The debug bridge to attach to
|
|
97
|
-
* @returns this for chaining
|
|
98
|
-
*/
|
|
99
|
-
attach(bridge: DebugBridge): this {
|
|
100
|
-
this._bridge = bridge
|
|
101
|
-
|
|
102
|
-
// Restore saved enabled state
|
|
103
|
-
const savedEnabled = this.get<boolean>('enabled')
|
|
104
|
-
if (savedEnabled !== null && bridge.enabled.value !== savedEnabled) {
|
|
105
|
-
bridge.enabled.value = savedEnabled
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Watch for enabled changes (if Vue's watch is available)
|
|
109
|
-
// In a real Vue app, use watch from vue
|
|
110
|
-
|
|
111
|
-
return this
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Detach from the debug bridge
|
|
116
|
-
*/
|
|
117
|
-
detach(): void {
|
|
118
|
-
if (this._unwatch) {
|
|
119
|
-
this._unwatch()
|
|
120
|
-
this._unwatch = null
|
|
121
|
-
}
|
|
122
|
-
this._bridge = null
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Save current bridge state
|
|
127
|
-
*/
|
|
128
|
-
save(): void {
|
|
129
|
-
if (!this._bridge) return
|
|
130
|
-
this.set('enabled', this._bridge.enabled.value)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Restore bridge state from localStorage
|
|
135
|
-
*/
|
|
136
|
-
restore(): void {
|
|
137
|
-
if (!this._bridge) return
|
|
138
|
-
const savedEnabled = this.get<boolean>('enabled')
|
|
139
|
-
if (savedEnabled !== null) {
|
|
140
|
-
this._bridge.enabled.value = savedEnabled
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Factory function to create a LocalStorageAdapter
|
|
147
|
-
* @param key - localStorage key prefix
|
|
148
|
-
*/
|
|
149
|
-
export function createLocalStorageAdapter(key?: string): LocalStorageAdapter {
|
|
150
|
-
return new LocalStorageAdapter(key)
|
|
151
|
-
}
|
|
7
|
+
export { LocalStorageAdapter, createLocalStorageAdapter } from '@quazardous/qddebug'
|