qdadm 0.15.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -1
- package/package.json +15 -2
- package/src/components/BoolCell.vue +11 -6
- package/src/components/forms/FormField.vue +64 -6
- package/src/components/forms/FormPage.vue +276 -0
- package/src/components/index.js +11 -0
- package/src/components/layout/AppLayout.vue +18 -9
- package/src/components/layout/BaseLayout.vue +183 -0
- package/src/components/layout/DashboardLayout.vue +100 -0
- package/src/components/layout/FormLayout.vue +261 -0
- package/src/components/layout/ListLayout.vue +334 -0
- package/src/components/layout/PageHeader.vue +6 -9
- package/src/components/layout/PageNav.vue +15 -0
- package/src/components/layout/Zone.vue +165 -0
- package/src/components/layout/defaults/DefaultBreadcrumb.vue +140 -0
- package/src/components/layout/defaults/DefaultFooter.vue +56 -0
- package/src/components/layout/defaults/DefaultFormActions.vue +53 -0
- package/src/components/layout/defaults/DefaultHeader.vue +69 -0
- package/src/components/layout/defaults/DefaultMenu.vue +197 -0
- package/src/components/layout/defaults/DefaultPagination.vue +79 -0
- package/src/components/layout/defaults/DefaultTable.vue +130 -0
- package/src/components/layout/defaults/DefaultToaster.vue +16 -0
- package/src/components/layout/defaults/DefaultUserInfo.vue +96 -0
- package/src/components/layout/defaults/index.js +17 -0
- package/src/composables/index.js +8 -6
- package/src/composables/useBreadcrumb.js +9 -5
- package/src/composables/useForm.js +135 -0
- package/src/composables/useFormPageBuilder.js +1154 -0
- package/src/composables/useHooks.js +53 -0
- package/src/composables/useLayoutResolver.js +260 -0
- package/src/composables/useListPageBuilder.js +336 -52
- package/src/composables/useNavContext.js +372 -0
- package/src/composables/useNavigation.js +38 -2
- package/src/composables/usePageTitle.js +59 -0
- package/src/composables/useSignals.js +49 -0
- package/src/composables/useZoneRegistry.js +162 -0
- package/src/core/bundles.js +406 -0
- package/src/core/decorator.js +322 -0
- package/src/core/extension.js +386 -0
- package/src/core/index.js +28 -0
- package/src/entity/EntityManager.js +314 -16
- package/src/entity/auth/AuthAdapter.js +125 -0
- package/src/entity/auth/PermissiveAdapter.js +64 -0
- package/src/entity/auth/index.js +11 -0
- package/src/entity/index.js +3 -0
- package/src/entity/storage/MockApiStorage.js +349 -0
- package/src/entity/storage/SdkStorage.js +478 -0
- package/src/entity/storage/index.js +2 -0
- package/src/hooks/HookRegistry.js +411 -0
- package/src/hooks/index.js +12 -0
- package/src/index.js +12 -0
- package/src/kernel/Kernel.js +141 -4
- package/src/kernel/SignalBus.js +180 -0
- package/src/kernel/index.js +7 -0
- package/src/module/moduleRegistry.js +124 -6
- package/src/orchestrator/Orchestrator.js +73 -1
- package/src/plugin.js +5 -0
- package/src/zones/ZoneRegistry.js +821 -0
- package/src/zones/index.js +16 -0
- package/src/zones/zones.js +189 -0
- package/src/composables/useEntityTitle.js +0 -121
- package/src/composables/useManager.js +0 -20
- package/src/composables/usePageBuilder.js +0 -334
- package/src/composables/useStatus.js +0 -146
- package/src/composables/useSubEditor.js +0 -165
- package/src/composables/useTabSync.js +0 -110
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SignalBus - Wrapper around QuarKernel for qdadm event-driven architecture
|
|
3
|
+
*
|
|
4
|
+
* Provides a clean API for entity lifecycle events and cross-component communication.
|
|
5
|
+
* Uses QuarKernel's wildcard support for flexible event subscriptions.
|
|
6
|
+
*
|
|
7
|
+
* Signal naming conventions:
|
|
8
|
+
* - Generic CRUD: entity:created, entity:updated, entity:deleted
|
|
9
|
+
* - Entity-specific: {entityName}:created, {entityName}:updated, {entityName}:deleted
|
|
10
|
+
*
|
|
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
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createKernel } from '@quazardous/quarkernel'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Signal names for entity operations
|
|
21
|
+
*/
|
|
22
|
+
export const SIGNALS = {
|
|
23
|
+
// Generic entity lifecycle signals
|
|
24
|
+
ENTITY_CREATED: 'entity:created',
|
|
25
|
+
ENTITY_UPDATED: 'entity:updated',
|
|
26
|
+
ENTITY_DELETED: 'entity:deleted',
|
|
27
|
+
|
|
28
|
+
// Pattern for entity-specific signals
|
|
29
|
+
// Use buildSignal(entityName, action) for these
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Actions for entity signals
|
|
34
|
+
*/
|
|
35
|
+
export const SIGNAL_ACTIONS = {
|
|
36
|
+
CREATED: 'created',
|
|
37
|
+
UPDATED: 'updated',
|
|
38
|
+
DELETED: 'deleted',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build an entity-specific signal name
|
|
43
|
+
* @param {string} entityName - Entity name (e.g., 'books', 'users')
|
|
44
|
+
* @param {string} action - Action from SIGNAL_ACTIONS
|
|
45
|
+
* @returns {string} Signal name (e.g., 'books:created')
|
|
46
|
+
*/
|
|
47
|
+
export function buildSignal(entityName, action) {
|
|
48
|
+
return `${entityName}:${action}`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* SignalBus class - wraps QuarKernel for qdadm
|
|
53
|
+
*/
|
|
54
|
+
export class SignalBus {
|
|
55
|
+
/**
|
|
56
|
+
* @param {object} options - QuarKernel options
|
|
57
|
+
* @param {boolean} options.debug - Enable debug logging
|
|
58
|
+
*/
|
|
59
|
+
constructor(options = {}) {
|
|
60
|
+
this._kernel = createKernel({
|
|
61
|
+
delimiter: ':',
|
|
62
|
+
wildcard: true,
|
|
63
|
+
errorBoundary: true,
|
|
64
|
+
debug: options.debug ?? false,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Emit a signal with payload
|
|
70
|
+
* @param {string} signal - Signal name
|
|
71
|
+
* @param {*} payload - Signal payload (typically { entity, data, context })
|
|
72
|
+
* @returns {Promise<void>}
|
|
73
|
+
*/
|
|
74
|
+
async emit(signal, payload) {
|
|
75
|
+
return this._kernel.emit(signal, payload)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Subscribe to a signal
|
|
80
|
+
* @param {string} signal - Signal name (supports wildcards via QuarKernel)
|
|
81
|
+
* @param {Function} handler - Handler function (event, ctx) => void
|
|
82
|
+
* @param {object} options - Listener options
|
|
83
|
+
* @param {number} options.priority - Listener priority (higher = earlier)
|
|
84
|
+
* @param {string} options.id - Unique listener ID
|
|
85
|
+
* @param {string|string[]} options.after - Run after these listener IDs
|
|
86
|
+
* @param {boolean} options.once - Remove after first call
|
|
87
|
+
* @returns {Function} Unbind function
|
|
88
|
+
*/
|
|
89
|
+
on(signal, handler, options = {}) {
|
|
90
|
+
return this._kernel.on(signal, handler, options)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Unsubscribe from a signal
|
|
95
|
+
* @param {string} signal - Signal name
|
|
96
|
+
* @param {Function} handler - Handler function to remove (optional, removes all if omitted)
|
|
97
|
+
*/
|
|
98
|
+
off(signal, handler) {
|
|
99
|
+
this._kernel.off(signal, handler)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Subscribe to a signal once (Promise-based)
|
|
104
|
+
* @param {string} signal - Signal name
|
|
105
|
+
* @param {object} options - Options
|
|
106
|
+
* @param {number} options.timeout - Timeout in ms
|
|
107
|
+
* @returns {Promise<object>} Event object
|
|
108
|
+
*/
|
|
109
|
+
once(signal, options = {}) {
|
|
110
|
+
return this._kernel.once(signal, options)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Emit an entity lifecycle signal
|
|
115
|
+
* @param {string} entityName - Entity name
|
|
116
|
+
* @param {string} action - Action from SIGNAL_ACTIONS
|
|
117
|
+
* @param {object} data - Entity data
|
|
118
|
+
* @returns {Promise<void>}
|
|
119
|
+
*/
|
|
120
|
+
async emitEntity(entityName, action, data) {
|
|
121
|
+
const specificSignal = buildSignal(entityName, action)
|
|
122
|
+
const genericSignal = buildSignal('entity', action)
|
|
123
|
+
|
|
124
|
+
// Emit both specific and generic signals
|
|
125
|
+
// Specific first, then generic
|
|
126
|
+
await this.emit(specificSignal, { entity: entityName, data })
|
|
127
|
+
await this.emit(genericSignal, { entity: entityName, data })
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get listener count for a signal
|
|
132
|
+
* @param {string} signal - Signal name (optional, total if omitted)
|
|
133
|
+
* @returns {number}
|
|
134
|
+
*/
|
|
135
|
+
listenerCount(signal) {
|
|
136
|
+
return this._kernel.listenerCount(signal)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get all registered signal names
|
|
141
|
+
* @returns {string[]}
|
|
142
|
+
*/
|
|
143
|
+
signalNames() {
|
|
144
|
+
return this._kernel.eventNames()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Remove all listeners
|
|
149
|
+
* @param {string} signal - Signal name (optional, all if omitted)
|
|
150
|
+
*/
|
|
151
|
+
offAll(signal) {
|
|
152
|
+
this._kernel.offAll(signal)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Enable/disable debug mode
|
|
157
|
+
* @param {boolean} enabled
|
|
158
|
+
*/
|
|
159
|
+
debug(enabled) {
|
|
160
|
+
this._kernel.debug(enabled)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get the underlying QuarKernel instance
|
|
165
|
+
* Used for sharing the kernel with HookRegistry
|
|
166
|
+
* @returns {QuarKernel}
|
|
167
|
+
*/
|
|
168
|
+
getKernel() {
|
|
169
|
+
return this._kernel
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Factory function to create a SignalBus instance
|
|
175
|
+
* @param {object} options - SignalBus options
|
|
176
|
+
* @returns {SignalBus}
|
|
177
|
+
*/
|
|
178
|
+
export function createSignalBus(options = {}) {
|
|
179
|
+
return new SignalBus(options)
|
|
180
|
+
}
|
package/src/kernel/index.js
CHANGED
|
@@ -36,6 +36,13 @@ const entityConfigs = new Map() // Entity declarations from modules
|
|
|
36
36
|
// Configurable section order (set via setSectionOrder or bootstrap)
|
|
37
37
|
let sectionOrder = []
|
|
38
38
|
|
|
39
|
+
// Altered nav sections after menu:alter hook
|
|
40
|
+
// null means not yet altered, use raw navSections
|
|
41
|
+
let alteredNavSections = null
|
|
42
|
+
|
|
43
|
+
// Promise for in-flight alteration (prevents concurrent calls)
|
|
44
|
+
let alterationPromise = null
|
|
45
|
+
|
|
39
46
|
/**
|
|
40
47
|
* Registry API passed to module init functions
|
|
41
48
|
*/
|
|
@@ -52,9 +59,10 @@ const registry = {
|
|
|
52
59
|
* @param {string} options.parent.foreignKey - Foreign key field (e.g., 'book_id')
|
|
53
60
|
* @param {string} [options.parent.itemRoute] - Override parent item route (auto: parentEntity.routePrefix + '-edit')
|
|
54
61
|
* @param {string} [options.label] - Label for navlinks (defaults to entity labelPlural)
|
|
62
|
+
* @param {string} [options.layout] - Default layout type for routes ('list', 'form', 'dashboard', 'base')
|
|
55
63
|
*/
|
|
56
64
|
addRoutes(prefix, moduleRoutes, options = {}) {
|
|
57
|
-
const { entity, parent, label } = options
|
|
65
|
+
const { entity, parent, label, layout } = options
|
|
58
66
|
const prefixedRoutes = moduleRoutes.map(route => ({
|
|
59
67
|
...route,
|
|
60
68
|
path: route.path ? `${prefix}/${route.path}` : prefix,
|
|
@@ -62,7 +70,9 @@ const registry = {
|
|
|
62
70
|
...route.meta,
|
|
63
71
|
...(entity && { entity }),
|
|
64
72
|
...(parent && { parent }),
|
|
65
|
-
...(label && { navLabel: label })
|
|
73
|
+
...(label && { navLabel: label }),
|
|
74
|
+
// Layout can be set per-route or inherited from options
|
|
75
|
+
...((route.meta?.layout || layout) && { layout: route.meta?.layout || layout })
|
|
66
76
|
}
|
|
67
77
|
}))
|
|
68
78
|
routes.push(...prefixedRoutes)
|
|
@@ -154,9 +164,10 @@ export function getRoutes() {
|
|
|
154
164
|
}
|
|
155
165
|
|
|
156
166
|
/**
|
|
157
|
-
*
|
|
167
|
+
* Build raw navigation sections from registered nav items (before alteration)
|
|
168
|
+
* @returns {Array<{title: string, items: Array}>}
|
|
158
169
|
*/
|
|
159
|
-
|
|
170
|
+
function buildRawNavSections() {
|
|
160
171
|
const sections = []
|
|
161
172
|
|
|
162
173
|
// First add sections in the configured order
|
|
@@ -164,7 +175,7 @@ export function getNavSections() {
|
|
|
164
175
|
if (navSections.has(title)) {
|
|
165
176
|
sections.push({
|
|
166
177
|
title,
|
|
167
|
-
items: navSections.get(title)
|
|
178
|
+
items: [...navSections.get(title)] // Clone items array
|
|
168
179
|
})
|
|
169
180
|
}
|
|
170
181
|
}
|
|
@@ -172,13 +183,118 @@ export function getNavSections() {
|
|
|
172
183
|
// Add any sections not in the predefined order
|
|
173
184
|
for (const [title, items] of navSections) {
|
|
174
185
|
if (!sectionOrder.includes(title)) {
|
|
175
|
-
sections.push({ title, items })
|
|
186
|
+
sections.push({ title, items: [...items] }) // Clone items array
|
|
176
187
|
}
|
|
177
188
|
}
|
|
178
189
|
|
|
179
190
|
return sections
|
|
180
191
|
}
|
|
181
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Get navigation sections in order
|
|
195
|
+
*
|
|
196
|
+
* Returns altered sections if menu:alter hook has been invoked,
|
|
197
|
+
* otherwise returns raw sections from module registrations.
|
|
198
|
+
*
|
|
199
|
+
* @returns {Array<{title: string, items: Array}>}
|
|
200
|
+
*/
|
|
201
|
+
export function getNavSections() {
|
|
202
|
+
// Return altered sections if available, otherwise build from raw
|
|
203
|
+
if (alteredNavSections !== null) {
|
|
204
|
+
return alteredNavSections
|
|
205
|
+
}
|
|
206
|
+
return buildRawNavSections()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Invoke menu:alter hook to allow modules to modify navigation structure
|
|
211
|
+
*
|
|
212
|
+
* Called lazily by useNavigation on first access. Modules can register
|
|
213
|
+
* handlers for 'menu:alter' hook to add, remove, reorder, or modify
|
|
214
|
+
* navigation sections and items.
|
|
215
|
+
*
|
|
216
|
+
* @param {HookRegistry} hooks - The hook registry instance
|
|
217
|
+
* @returns {Promise<void>}
|
|
218
|
+
*
|
|
219
|
+
* @typedef {Object} MenuAlterContext
|
|
220
|
+
* @property {Array<MenuSection>} sections - Navigation sections (mutable)
|
|
221
|
+
*
|
|
222
|
+
* @typedef {Object} MenuSection
|
|
223
|
+
* @property {string} title - Section title
|
|
224
|
+
* @property {Array<NavItem>} items - Navigation items in this section
|
|
225
|
+
*
|
|
226
|
+
* @typedef {Object} NavItem
|
|
227
|
+
* @property {string} route - Route name
|
|
228
|
+
* @property {string} label - Display label
|
|
229
|
+
* @property {string} [icon] - Icon class (e.g., 'pi pi-users')
|
|
230
|
+
* @property {string} [entity] - Entity name for permission checks
|
|
231
|
+
* @property {boolean} [exact] - Use exact route matching
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* // In module init or extension
|
|
235
|
+
* hooks.register('menu:alter', (context) => {
|
|
236
|
+
* // Add a new section
|
|
237
|
+
* context.sections.push({
|
|
238
|
+
* title: 'Tools',
|
|
239
|
+
* items: [{ route: 'tools', label: 'Tools', icon: 'pi pi-wrench' }]
|
|
240
|
+
* })
|
|
241
|
+
*
|
|
242
|
+
* // Add item to existing section
|
|
243
|
+
* const adminSection = context.sections.find(s => s.title === 'Admin')
|
|
244
|
+
* if (adminSection) {
|
|
245
|
+
* adminSection.items.push({ route: 'settings', label: 'Settings' })
|
|
246
|
+
* }
|
|
247
|
+
*
|
|
248
|
+
* // Remove a section
|
|
249
|
+
* const idx = context.sections.findIndex(s => s.title === 'Legacy')
|
|
250
|
+
* if (idx !== -1) context.sections.splice(idx, 1)
|
|
251
|
+
*
|
|
252
|
+
* return context
|
|
253
|
+
* })
|
|
254
|
+
*/
|
|
255
|
+
export async function alterMenuSections(hooks) {
|
|
256
|
+
// Already altered? Return immediately
|
|
257
|
+
if (alteredNavSections !== null) {
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// In-flight alteration? Wait for it
|
|
262
|
+
if (alterationPromise !== null) {
|
|
263
|
+
await alterationPromise
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// No hooks registry? Mark as altered with raw sections
|
|
268
|
+
if (!hooks) {
|
|
269
|
+
alteredNavSections = buildRawNavSections()
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Perform alteration
|
|
274
|
+
alterationPromise = (async () => {
|
|
275
|
+
const rawSections = buildRawNavSections()
|
|
276
|
+
|
|
277
|
+
// Invoke menu:alter hook with mutable context
|
|
278
|
+
const alteredContext = await hooks.alter('menu:alter', {
|
|
279
|
+
sections: rawSections
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
// Store altered sections for getNavSections()
|
|
283
|
+
alteredNavSections = alteredContext.sections
|
|
284
|
+
alterationPromise = null
|
|
285
|
+
})()
|
|
286
|
+
|
|
287
|
+
await alterationPromise
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Check if menu:alter hook has been invoked
|
|
292
|
+
* @returns {boolean}
|
|
293
|
+
*/
|
|
294
|
+
export function isMenuAltered() {
|
|
295
|
+
return alteredNavSections !== null
|
|
296
|
+
}
|
|
297
|
+
|
|
182
298
|
/**
|
|
183
299
|
* Get route families mapping
|
|
184
300
|
*/
|
|
@@ -239,6 +355,8 @@ export function resetRegistry() {
|
|
|
239
355
|
navSections.clear()
|
|
240
356
|
entityConfigs.clear()
|
|
241
357
|
sectionOrder = []
|
|
358
|
+
alteredNavSections = null
|
|
359
|
+
alterationPromise = null
|
|
242
360
|
}
|
|
243
361
|
|
|
244
362
|
// Export registry for direct access if needed
|
|
@@ -35,11 +35,20 @@ export class Orchestrator {
|
|
|
35
35
|
const {
|
|
36
36
|
entityFactory = null,
|
|
37
37
|
// Optional: pre-registered managers (for special cases)
|
|
38
|
-
managers = {}
|
|
38
|
+
managers = {},
|
|
39
|
+
// SignalBus instance for event-driven communication
|
|
40
|
+
signals = null,
|
|
41
|
+
// HookRegistry instance for lifecycle hooks
|
|
42
|
+
hooks = null,
|
|
43
|
+
// Optional: AuthAdapter for entity permission checks (scope/silo)
|
|
44
|
+
entityAuthAdapter = null
|
|
39
45
|
} = options
|
|
40
46
|
|
|
41
47
|
this._entityFactory = entityFactory
|
|
42
48
|
this._managers = new Map()
|
|
49
|
+
this._signals = signals
|
|
50
|
+
this._hooks = hooks
|
|
51
|
+
this._entityAuthAdapter = entityAuthAdapter
|
|
43
52
|
|
|
44
53
|
// Register pre-provided managers
|
|
45
54
|
for (const [name, manager] of Object.entries(managers)) {
|
|
@@ -47,6 +56,56 @@ export class Orchestrator {
|
|
|
47
56
|
}
|
|
48
57
|
}
|
|
49
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Set the SignalBus instance
|
|
61
|
+
* @param {SignalBus} signals
|
|
62
|
+
*/
|
|
63
|
+
setSignals(signals) {
|
|
64
|
+
this._signals = signals
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get the SignalBus instance
|
|
69
|
+
* @returns {SignalBus|null}
|
|
70
|
+
*/
|
|
71
|
+
get signals() {
|
|
72
|
+
return this._signals
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Set the entity AuthAdapter for permission checks
|
|
77
|
+
* This adapter will be injected into all newly registered managers
|
|
78
|
+
* (unless they already have their own adapter)
|
|
79
|
+
* @param {AuthAdapter} adapter
|
|
80
|
+
*/
|
|
81
|
+
setEntityAuthAdapter(adapter) {
|
|
82
|
+
this._entityAuthAdapter = adapter
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the entity AuthAdapter
|
|
87
|
+
* @returns {AuthAdapter|null}
|
|
88
|
+
*/
|
|
89
|
+
get entityAuthAdapter() {
|
|
90
|
+
return this._entityAuthAdapter
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Set the HookRegistry instance
|
|
95
|
+
* @param {HookRegistry} hooks
|
|
96
|
+
*/
|
|
97
|
+
setHooks(hooks) {
|
|
98
|
+
this._hooks = hooks
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the HookRegistry instance
|
|
103
|
+
* @returns {HookRegistry|null}
|
|
104
|
+
*/
|
|
105
|
+
get hooks() {
|
|
106
|
+
return this._hooks
|
|
107
|
+
}
|
|
108
|
+
|
|
50
109
|
/**
|
|
51
110
|
* Set the entity factory
|
|
52
111
|
* @param {function} factory - (entityName, entityConfig) => EntityManager
|
|
@@ -64,6 +123,19 @@ export class Orchestrator {
|
|
|
64
123
|
*/
|
|
65
124
|
register(name, manager) {
|
|
66
125
|
this._managers.set(name, manager)
|
|
126
|
+
// Pass signals reference to manager for event emission
|
|
127
|
+
if (this._signals && manager.setSignals) {
|
|
128
|
+
manager.setSignals(this._signals)
|
|
129
|
+
}
|
|
130
|
+
// Pass hooks reference to manager for lifecycle hooks
|
|
131
|
+
if (this._hooks && manager.setHooks) {
|
|
132
|
+
manager.setHooks(this._hooks)
|
|
133
|
+
}
|
|
134
|
+
// Inject entityAuthAdapter if provided and manager doesn't have one
|
|
135
|
+
// Manager's own adapter takes precedence (allows per-entity customization)
|
|
136
|
+
if (this._entityAuthAdapter && !manager._authAdapter) {
|
|
137
|
+
manager.authAdapter = this._entityAuthAdapter
|
|
138
|
+
}
|
|
67
139
|
if (manager.onRegister) {
|
|
68
140
|
manager.onRegister(this)
|
|
69
141
|
}
|
package/src/plugin.js
CHANGED
|
@@ -22,6 +22,7 @@ import qdadmLogo from './assets/logo.svg'
|
|
|
22
22
|
* @param {object} options.features - Optional: Feature toggles (auth, poweredBy)
|
|
23
23
|
* @param {object} options.builtinModules - Optional: Builtin modules configuration
|
|
24
24
|
* @param {object} options.endpoints - Optional: API endpoints configuration
|
|
25
|
+
* @param {string} options.homeRoute - Optional: Home route name for breadcrumb
|
|
25
26
|
* @returns {object} Vue plugin
|
|
26
27
|
*/
|
|
27
28
|
export function createQdadm(options) {
|
|
@@ -112,6 +113,10 @@ export function createQdadm(options) {
|
|
|
112
113
|
app.provide('qdadmSectionOrder', options.modules.sectionOrder)
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
if (options.homeRoute) {
|
|
117
|
+
app.provide('qdadmHomeRoute', options.homeRoute)
|
|
118
|
+
}
|
|
119
|
+
|
|
115
120
|
// Add route guard for entity permissions
|
|
116
121
|
options.router.beforeEach((to, from, next) => {
|
|
117
122
|
const entity = to.meta?.entity
|