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.
Files changed (86) hide show
  1. package/package.json +8 -4
  2. package/src/chain/ActiveStack.ts +79 -98
  3. package/src/chain/StackHydrator.ts +3 -2
  4. package/src/chain/index.ts +7 -1
  5. package/src/components/QdadmRoot.vue +52 -0
  6. package/src/components/edit/FormActions.vue +9 -6
  7. package/src/components/edit/LookupPickerDialog.vue +6 -3
  8. package/src/components/index.ts +6 -0
  9. package/src/composables/useEntityItemFormPage.ts +1 -0
  10. package/src/composables/useEntityItemShowPage.ts +1 -0
  11. package/src/composables/useFieldManager.ts +100 -3
  12. package/src/composables/useListPage.ts +50 -59
  13. package/src/composables/useListPage.utils.ts +101 -0
  14. package/src/composables/useNavigation.ts +26 -3
  15. package/src/composables/useOptionsLookup.ts +5 -1
  16. package/src/gen/generateManagers.test.js +27 -0
  17. package/src/gen/generateManagers.ts +12 -0
  18. package/src/hooks/HookRegistry.ts +14 -435
  19. package/src/i18n/I18n.ts +344 -0
  20. package/src/i18n/IncrementalDomainProvider.ts +153 -0
  21. package/src/i18n/InlineTranslationProvider.ts +4 -0
  22. package/src/i18n/LazyTranslationProvider.ts +102 -0
  23. package/src/i18n/MessagesRegistry.ts +4 -0
  24. package/src/i18n/Resolver.ts +4 -0
  25. package/src/i18n/__tests__/I18n.test.ts +169 -0
  26. package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
  27. package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
  28. package/src/i18n/__tests__/Resolver.test.ts +271 -0
  29. package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
  30. package/src/i18n/defaults/core.en.yml +55 -0
  31. package/src/i18n/defaults/core.fr.yml +55 -0
  32. package/src/i18n/index.ts +55 -0
  33. package/src/i18n/loaders/raw-modules.d.ts +15 -0
  34. package/src/i18n/loaders/yaml.ts +35 -0
  35. package/src/i18n/strategies.ts +4 -0
  36. package/src/i18n/types.ts +34 -0
  37. package/src/i18n/useI18n.ts +34 -0
  38. package/src/index.ts +37 -0
  39. package/src/kernel/EventRouter.ts +17 -300
  40. package/src/kernel/Kernel.i18n.ts +29 -0
  41. package/src/kernel/Kernel.modules.ts +6 -0
  42. package/src/kernel/Kernel.registries.ts +10 -2
  43. package/src/kernel/Kernel.routing.ts +43 -1
  44. package/src/kernel/Kernel.ts +43 -0
  45. package/src/kernel/Kernel.types.ts +52 -1
  46. package/src/kernel/Kernel.vue.ts +121 -15
  47. package/src/kernel/KernelContext.entities.ts +80 -0
  48. package/src/kernel/KernelContext.events.ts +57 -0
  49. package/src/kernel/KernelContext.i18n.ts +37 -0
  50. package/src/kernel/KernelContext.permissions.ts +38 -0
  51. package/src/kernel/KernelContext.routing.ts +280 -0
  52. package/src/kernel/KernelContext.ts +125 -834
  53. package/src/kernel/KernelContext.types.ts +173 -0
  54. package/src/kernel/KernelContext.zones.ts +54 -0
  55. package/src/kernel/SSEBridge.ts +7 -362
  56. package/src/kernel/SignalBus.ts +24 -148
  57. package/src/modules/debug/AuthCollector.ts +48 -1
  58. package/src/modules/debug/Collector.ts +16 -302
  59. package/src/modules/debug/DebugBridge.ts +10 -171
  60. package/src/modules/debug/DebugModule.ts +35 -5
  61. package/src/modules/debug/EntitiesCollector.ts +97 -1
  62. package/src/modules/debug/ErrorCollector.ts +2 -77
  63. package/src/modules/debug/I18nCollector.ts +9 -0
  64. package/src/modules/debug/LocalStorageAdapter.ts +3 -147
  65. package/src/modules/debug/RouterCollector.ts +101 -1
  66. package/src/modules/debug/SignalCollector.ts +2 -150
  67. package/src/modules/debug/ToastCollector.ts +2 -91
  68. package/src/modules/debug/ZonesCollector.ts +93 -1
  69. package/src/modules/debug/components/DebugBar.vue +19 -775
  70. package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
  71. package/src/modules/debug/components/index.ts +4 -3
  72. package/src/modules/debug/components/panels/AuthPanel.vue +1 -1
  73. package/src/modules/debug/components/panels/EntitiesPanel.vue +5 -5
  74. package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
  75. package/src/modules/debug/components/panels/RouterPanel.vue +1 -1
  76. package/src/modules/debug/components/panels/index.ts +10 -4
  77. package/src/modules/debug/index.ts +15 -0
  78. package/src/modules/debug/styles.scss +22 -18
  79. package/src/utils/index.ts +0 -3
  80. package/src/vite/qdadmDebugPlugin.ts +401 -0
  81. package/src/vite-env.d.ts +16 -0
  82. package/src/modules/debug/components/ObjectTree.vue +0 -123
  83. package/src/modules/debug/components/panels/EntriesPanel.vue +0 -100
  84. package/src/modules/debug/components/panels/SignalsPanel.vue +0 -188
  85. package/src/modules/debug/components/panels/ToastsPanel.vue +0 -45
  86. package/src/utils/debugInjector.ts +0 -306
@@ -1,28 +1,36 @@
1
1
  /**
2
- * SignalBus - Wrapper around QuarKernel for qdadm event-driven architecture
2
+ * SignalBus - qdadm wiring.
3
3
  *
4
- * Provides a clean API for entity lifecycle events and cross-component communication.
5
- * Uses QuarKernel's wildcard support for flexible event subscriptions.
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, {entityName}:updated, {entityName}:deleted
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 (via QuarKernel):
12
- * - 'entity:*' matches entity:created, entity:updated, entity:deleted
13
- * - 'books:*' matches books:created, books:updated, books:deleted
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
- import {
18
- createKernel,
19
- type QuarKernel,
20
- type ListenerCallback,
21
- type ListenerOptions,
22
- } from '@quazardous/quarkernel'
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
- * Signal names for entity operations
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 { Collector, type CollectorContext, type CollectorEntry, type CollectorOptions } from './Collector'
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 - Base class for debug collectors
2
+ * Collector qdadm re-export.
3
3
  *
4
- * Collectors gather data from various sources (signals, network, events)
5
- * and store them in a ring buffer for display in the debug panel.
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
- * Internal uninstall - override in subclass
301
- * @protected
302
- */
303
- protected _doUninstall(): void {
304
- // Override in subclass
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'