qdadm 1.13.1 → 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 +7 -3
- 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,8 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* KernelContext - Fluent API wrapper for module registration
|
|
2
|
+
* KernelContext - Fluent API wrapper for module registration.
|
|
3
3
|
*
|
|
4
4
|
* Provides a chainable interface for modules to register entities, routes,
|
|
5
|
-
* navigation items, zones, blocks,
|
|
5
|
+
* navigation items, zones, blocks, signal handlers, hooks, permissions and
|
|
6
|
+
* translations.
|
|
7
|
+
*
|
|
8
|
+
* The class shell here keeps the constructor + service getters; the fluent
|
|
9
|
+
* registration methods live in dedicated patch modules following the same
|
|
10
|
+
* pattern as `Kernel.ts`:
|
|
11
|
+
*
|
|
12
|
+
* - KernelContext.entities.ts — entity, userEntity
|
|
13
|
+
* - KernelContext.routing.ts — routes, navItem, routeFamily, crud, childPage
|
|
14
|
+
* - KernelContext.zones.ts — zone, block, provide, component
|
|
15
|
+
* - KernelContext.events.ts — on, hook, defer
|
|
16
|
+
* - KernelContext.permissions.ts — permissions, entityPermissions
|
|
17
|
+
* - KernelContext.i18n.ts — messages, aliases, messagesProvider
|
|
6
18
|
*
|
|
7
19
|
* Usage in a module:
|
|
8
20
|
* ```ts
|
|
@@ -15,7 +27,6 @@
|
|
|
15
27
|
* { path: ':id', name: 'users-edit', component: UserEdit }
|
|
16
28
|
* ])
|
|
17
29
|
* .navItem({ section: 'Admin', route: 'users', icon: 'pi pi-users', label: 'Users' })
|
|
18
|
-
* .routeFamily('users', ['users-'])
|
|
19
30
|
* .zone('users-list-header')
|
|
20
31
|
* .block('users-list-header', { component: UserStats, weight: 10 })
|
|
21
32
|
* .on('users:created', (event) => console.log('User created:', event))
|
|
@@ -26,11 +37,8 @@
|
|
|
26
37
|
|
|
27
38
|
import type { App, Component } from 'vue'
|
|
28
39
|
import type { Router, RouteRecordRaw } from 'vue-router'
|
|
29
|
-
import { managerFactory, type ManagerFactoryContext } from '../entity/factory.js'
|
|
30
|
-
import { registry, getRoutes } from '../module/moduleRegistry'
|
|
31
|
-
import { UsersManager, type UsersManagerOptions } from '../security/UsersManager'
|
|
32
|
-
import type { SignalBus } from './SignalBus'
|
|
33
40
|
import type { ListenerOptions } from '@quazardous/quarkernel'
|
|
41
|
+
import type { SignalBus } from './SignalBus'
|
|
34
42
|
import type { Orchestrator } from '../orchestrator/Orchestrator'
|
|
35
43
|
import type { HookRegistry } from '../hooks/HookRegistry'
|
|
36
44
|
import type { ZoneRegistry } from '../zones/ZoneRegistry'
|
|
@@ -38,192 +46,60 @@ import type { DeferredRegistry } from '../deferred/DeferredRegistry.js'
|
|
|
38
46
|
import type { SecurityChecker } from '../entity/auth/SecurityChecker'
|
|
39
47
|
import type { PermissionRegistry } from '../security/PermissionRegistry'
|
|
40
48
|
import type { EntityManager } from '../entity/EntityManager'
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
stackHydrator: unknown
|
|
89
|
-
options: KernelOptions
|
|
90
|
-
_pendingProvides: Map<string | symbol, unknown>
|
|
91
|
-
_pendingComponents: Map<string, Component>
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Navigation item configuration
|
|
96
|
-
*/
|
|
97
|
-
export interface NavItem {
|
|
98
|
-
section: string
|
|
99
|
-
route: string
|
|
100
|
-
label: string
|
|
101
|
-
icon?: string
|
|
102
|
-
exact?: boolean
|
|
103
|
-
entity?: string
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Zone configuration
|
|
108
|
-
*/
|
|
109
|
-
export interface ZoneOptions {
|
|
110
|
-
default?: Component
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Block configuration
|
|
115
|
-
*/
|
|
116
|
-
export interface BlockConfig {
|
|
117
|
-
component: Component
|
|
118
|
-
weight?: number
|
|
119
|
-
props?: Record<string, unknown>
|
|
120
|
-
id?: string
|
|
121
|
-
operation?: 'add' | 'replace' | 'extend' | 'wrap'
|
|
122
|
-
/** Block ID to replace (required if operation='replace') */
|
|
123
|
-
replaces?: string
|
|
124
|
-
/** Block ID to insert before (for operation='extend') */
|
|
125
|
-
before?: string
|
|
126
|
-
/** Block ID to insert after (for operation='extend') */
|
|
127
|
-
after?: string
|
|
128
|
-
/** Block ID to wrap (required if operation='wrap') */
|
|
129
|
-
wraps?: string
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Route options
|
|
134
|
-
*/
|
|
135
|
-
export interface RouteOptions {
|
|
136
|
-
entity?: string
|
|
137
|
-
parent?: ParentConfig
|
|
138
|
-
label?: string
|
|
139
|
-
layout?: string
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Parent route configuration
|
|
144
|
-
*/
|
|
145
|
-
interface ParentConfig {
|
|
146
|
-
entity: string
|
|
147
|
-
param: string
|
|
148
|
-
foreignKey?: string
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* CRUD page components
|
|
153
|
-
*/
|
|
154
|
-
export interface CrudPages {
|
|
155
|
-
list?: () => Promise<{ default: Component }>
|
|
156
|
-
show?: () => Promise<{ default: Component }>
|
|
157
|
-
form?: () => Promise<{ default: Component }>
|
|
158
|
-
create?: () => Promise<{ default: Component }>
|
|
159
|
-
edit?: () => Promise<{ default: Component }>
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* CRUD options
|
|
164
|
-
*/
|
|
165
|
-
export interface CrudOptions {
|
|
166
|
-
nav?: {
|
|
167
|
-
section: string
|
|
168
|
-
icon?: string
|
|
169
|
-
label?: string
|
|
170
|
-
}
|
|
171
|
-
routePrefix?: string
|
|
172
|
-
/** Override the URL path segment (defaults to entity name). Useful for camelCase entities like 'jobTasks' → 'tasks'. */
|
|
173
|
-
pathSegment?: string
|
|
174
|
-
parentRoute?: string
|
|
175
|
-
foreignKey?: string
|
|
176
|
-
label?: string
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Child page options for non-entity child routes
|
|
181
|
-
*/
|
|
182
|
-
export interface ChildPageOptions {
|
|
183
|
-
component: () => Promise<{ default: Component }>
|
|
184
|
-
label?: string
|
|
185
|
-
icon?: string
|
|
186
|
-
meta?: Record<string, unknown>
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* User entity options
|
|
191
|
-
*/
|
|
192
|
-
export type UserEntityOptions = UsersManagerOptions
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Permission metadata
|
|
196
|
-
*/
|
|
197
|
-
export interface PermissionMeta {
|
|
198
|
-
label: string
|
|
199
|
-
description?: string
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Permission options
|
|
204
|
-
*/
|
|
205
|
-
export interface PermissionOptions {
|
|
206
|
-
isEntity?: boolean
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Signal handler function type
|
|
211
|
-
*/
|
|
212
|
-
type SignalHandler = (event: { data: unknown; [key: string]: unknown }) => void
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Hook handler function type
|
|
216
|
-
*/
|
|
217
|
-
type HookHandler = (context: unknown) => unknown | Promise<unknown>
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Deferred factory function type
|
|
221
|
-
*/
|
|
222
|
-
type DeferredFactory = () => Promise<void>
|
|
49
|
+
import type { I18n } from '../i18n/I18n'
|
|
50
|
+
import type { AliasPattern, MessagesBundle, TranslationProvider } from '../i18n/types'
|
|
51
|
+
|
|
52
|
+
// Internal types (shared with patch modules, not exported here).
|
|
53
|
+
import type {
|
|
54
|
+
AuthAdapter,
|
|
55
|
+
DeferredFactory,
|
|
56
|
+
HookHandler,
|
|
57
|
+
KernelInterface,
|
|
58
|
+
ModuleWithCleanup,
|
|
59
|
+
SignalHandler,
|
|
60
|
+
} from './KernelContext.types'
|
|
61
|
+
|
|
62
|
+
// Public types — re-exported to keep the existing barrel API stable.
|
|
63
|
+
export type {
|
|
64
|
+
BlockConfig,
|
|
65
|
+
ChildPageOptions,
|
|
66
|
+
CrudOptions,
|
|
67
|
+
CrudPages,
|
|
68
|
+
NavItem,
|
|
69
|
+
ParentConfig,
|
|
70
|
+
PermissionMeta,
|
|
71
|
+
PermissionOptions,
|
|
72
|
+
RouteOptions,
|
|
73
|
+
UserEntityOptions,
|
|
74
|
+
ZoneOptions,
|
|
75
|
+
} from './KernelContext.types'
|
|
76
|
+
import type {
|
|
77
|
+
BlockConfig,
|
|
78
|
+
ChildPageOptions,
|
|
79
|
+
CrudOptions,
|
|
80
|
+
CrudPages,
|
|
81
|
+
NavItem,
|
|
82
|
+
PermissionMeta,
|
|
83
|
+
PermissionOptions,
|
|
84
|
+
RouteOptions,
|
|
85
|
+
UserEntityOptions,
|
|
86
|
+
ZoneOptions,
|
|
87
|
+
} from './KernelContext.types'
|
|
88
|
+
|
|
89
|
+
// Prototype-patched method groups
|
|
90
|
+
import { applyEntityMethods } from './KernelContext.entities'
|
|
91
|
+
import { applyRoutingMethods } from './KernelContext.routing'
|
|
92
|
+
import { applyZoneMethods } from './KernelContext.zones'
|
|
93
|
+
import { applyEventMethods } from './KernelContext.events'
|
|
94
|
+
import { applyPermissionMethods } from './KernelContext.permissions'
|
|
95
|
+
import { applyI18nMethods } from './KernelContext.i18n'
|
|
223
96
|
|
|
224
97
|
export class KernelContext {
|
|
225
|
-
|
|
226
|
-
private
|
|
98
|
+
// Marked public so the patch modules (which read this.* via Self=any) can
|
|
99
|
+
// see them. They were `private` in the previous monolithic file but were
|
|
100
|
+
// already accessed across patch boundaries the moment we split.
|
|
101
|
+
_kernel: KernelInterface
|
|
102
|
+
_module: ModuleWithCleanup | null
|
|
227
103
|
|
|
228
104
|
constructor(kernel: KernelInterface, module: ModuleWithCleanup | null) {
|
|
229
105
|
this._kernel = kernel
|
|
@@ -234,724 +110,139 @@ export class KernelContext {
|
|
|
234
110
|
// Getters for kernel services
|
|
235
111
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
236
112
|
|
|
237
|
-
/**
|
|
238
|
-
* Get Vue app instance
|
|
239
|
-
*/
|
|
113
|
+
/** Vue app instance */
|
|
240
114
|
get app(): App | null {
|
|
241
115
|
return this._kernel.vueApp
|
|
242
116
|
}
|
|
243
117
|
|
|
244
|
-
/**
|
|
245
|
-
* Get Vue router instance
|
|
246
|
-
*/
|
|
118
|
+
/** Vue router instance */
|
|
247
119
|
get router(): Router | null {
|
|
248
120
|
return this._kernel.router
|
|
249
121
|
}
|
|
250
122
|
|
|
251
|
-
/**
|
|
252
|
-
* Get SignalBus instance
|
|
253
|
-
*/
|
|
123
|
+
/** SignalBus instance */
|
|
254
124
|
get signals(): SignalBus | null {
|
|
255
125
|
return this._kernel.signals
|
|
256
126
|
}
|
|
257
127
|
|
|
258
|
-
/**
|
|
259
|
-
* Get Orchestrator instance (entity managers)
|
|
260
|
-
*/
|
|
128
|
+
/** Orchestrator instance (entity managers) */
|
|
261
129
|
get orchestrator(): Orchestrator | null {
|
|
262
130
|
return this._kernel.orchestrator
|
|
263
131
|
}
|
|
264
132
|
|
|
265
|
-
/**
|
|
266
|
-
* Get ZoneRegistry instance
|
|
267
|
-
*/
|
|
133
|
+
/** ZoneRegistry instance */
|
|
268
134
|
get zones(): ZoneRegistry | null {
|
|
269
135
|
return this._kernel.zoneRegistry
|
|
270
136
|
}
|
|
271
137
|
|
|
272
|
-
/**
|
|
273
|
-
* Get HookRegistry instance
|
|
274
|
-
*/
|
|
138
|
+
/** HookRegistry instance */
|
|
275
139
|
get hooks(): HookRegistry | null {
|
|
276
140
|
return this._kernel.hookRegistry
|
|
277
141
|
}
|
|
278
142
|
|
|
279
|
-
/**
|
|
280
|
-
* Get DeferredRegistry instance
|
|
281
|
-
*/
|
|
143
|
+
/** DeferredRegistry instance */
|
|
282
144
|
get deferred(): DeferredRegistry | null {
|
|
283
145
|
return this._kernel.deferred
|
|
284
146
|
}
|
|
285
147
|
|
|
286
|
-
/**
|
|
287
|
-
* Check if running in development mode
|
|
288
|
-
*/
|
|
148
|
+
/** Whether running in development mode */
|
|
289
149
|
get isDev(): boolean {
|
|
290
150
|
return (import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV ?? false
|
|
291
151
|
}
|
|
292
152
|
|
|
293
|
-
/**
|
|
294
|
-
* Get debug mode from kernel options
|
|
295
|
-
*/
|
|
153
|
+
/** Debug mode from kernel options */
|
|
296
154
|
get debug(): boolean {
|
|
297
155
|
return this._kernel.options?.debug ?? false
|
|
298
156
|
}
|
|
299
157
|
|
|
300
|
-
/**
|
|
301
|
-
* Get auth adapter
|
|
302
|
-
*/
|
|
158
|
+
/** Auth adapter */
|
|
303
159
|
get authAdapter(): AuthAdapter | null {
|
|
304
160
|
return this._kernel.options?.authAdapter ?? null
|
|
305
161
|
}
|
|
306
162
|
|
|
307
|
-
/**
|
|
308
|
-
* Get security checker (role hierarchy, permissions)
|
|
309
|
-
*/
|
|
163
|
+
/** Security checker (role hierarchy, permissions) */
|
|
310
164
|
get security(): SecurityChecker | null {
|
|
311
165
|
return this._kernel.securityChecker
|
|
312
166
|
}
|
|
313
167
|
|
|
314
|
-
/**
|
|
315
|
-
* Get permission registry
|
|
316
|
-
*/
|
|
168
|
+
/** Permission registry */
|
|
317
169
|
get permissionRegistry(): PermissionRegistry | null {
|
|
318
170
|
return this._kernel.permissionRegistry
|
|
319
171
|
}
|
|
320
172
|
|
|
321
|
-
/**
|
|
322
|
-
* Get auth adapter shortcut
|
|
323
|
-
*/
|
|
173
|
+
/** Auth adapter shortcut */
|
|
324
174
|
get auth(): AuthAdapter | null {
|
|
325
175
|
return this._kernel.options?.authAdapter ?? null
|
|
326
176
|
}
|
|
327
177
|
|
|
328
|
-
/**
|
|
329
|
-
* Get ActiveStack instance (sync navigation context)
|
|
330
|
-
*/
|
|
178
|
+
/** ActiveStack instance (sync navigation context) */
|
|
331
179
|
get activeStack(): unknown {
|
|
332
180
|
return this._kernel.activeStack ?? null
|
|
333
181
|
}
|
|
334
182
|
|
|
335
|
-
/**
|
|
336
|
-
* Get StackHydrator instance (async data loading)
|
|
337
|
-
*/
|
|
183
|
+
/** StackHydrator instance (async data loading) */
|
|
338
184
|
get stackHydrator(): unknown {
|
|
339
185
|
return this._kernel.stackHydrator ?? null
|
|
340
186
|
}
|
|
341
187
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Register an entity manager
|
|
348
|
-
*
|
|
349
|
-
* Uses managerFactory to resolve config into a manager instance.
|
|
350
|
-
* Registers with Orchestrator for CRUD operations.
|
|
351
|
-
*
|
|
352
|
-
* @param name - Entity name (e.g., 'users', 'books')
|
|
353
|
-
* @param config - Manager config
|
|
354
|
-
* - String pattern: 'api:/api/users' → creates ApiStorage + EntityManager
|
|
355
|
-
* - Object: { storage: '...', label: '...', fields: {...} }
|
|
356
|
-
* - Manager instance: passed through directly
|
|
357
|
-
*
|
|
358
|
-
* @example
|
|
359
|
-
* ctx.entity('users', 'api:/api/users')
|
|
360
|
-
* ctx.entity('books', { storage: 'api:/api/books', label: 'Book' })
|
|
361
|
-
* ctx.entity('settings', new SettingsManager({...}))
|
|
362
|
-
*/
|
|
363
|
-
entity(name: string, config: string | Record<string, unknown> | EntityManager): this {
|
|
364
|
-
// Build factory context from kernel options
|
|
365
|
-
const factoryContext = {
|
|
366
|
-
storageResolver: this._kernel.options.storageResolver,
|
|
367
|
-
managerResolver: this._kernel.options.managerResolver,
|
|
368
|
-
managerRegistry: this._kernel.options.managerRegistry || {},
|
|
369
|
-
} as ManagerFactoryContext
|
|
370
|
-
|
|
371
|
-
// Create manager via factory
|
|
372
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
373
|
-
const manager = managerFactory(config as any, name, factoryContext)
|
|
374
|
-
|
|
375
|
-
// Register with orchestrator
|
|
376
|
-
if (this._kernel.orchestrator) {
|
|
377
|
-
this._kernel.orchestrator.register(name, manager)
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Auto-register CRUD permissions for this entity
|
|
381
|
-
if (this._kernel.permissionRegistry) {
|
|
382
|
-
this._kernel.permissionRegistry.registerEntity(name, {
|
|
383
|
-
module: (this._module?.constructor as { name?: string })?.name || 'unknown',
|
|
384
|
-
// Register entity-own:* permissions if manager has isOwn configured
|
|
385
|
-
hasOwnership: !!(manager as unknown as { _isOwn?: unknown })._isOwn,
|
|
386
|
-
})
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
return this
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* Register a users entity with standard fields and role linking
|
|
394
|
-
*
|
|
395
|
-
* Creates a UsersManager with:
|
|
396
|
-
* - username, password, role fields (role linked to roles entity)
|
|
397
|
-
* - System entity flag
|
|
398
|
-
* - Admin-only access by default
|
|
399
|
-
*
|
|
400
|
-
* @param options - Configuration options
|
|
401
|
-
*
|
|
402
|
-
* @example
|
|
403
|
-
* // Basic usage with MockApiStorage
|
|
404
|
-
* ctx.userEntity({
|
|
405
|
-
* storage: new MockApiStorage({ entityName: 'users', initialData: usersFixture })
|
|
406
|
-
* })
|
|
407
|
-
*/
|
|
408
|
-
userEntity(options: UserEntityOptions): this {
|
|
409
|
-
const manager = new UsersManager(options)
|
|
410
|
-
|
|
411
|
-
// Register with orchestrator
|
|
412
|
-
if (this._kernel.orchestrator) {
|
|
413
|
-
this._kernel.orchestrator.register('users', manager)
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Auto-register CRUD permissions for users entity
|
|
417
|
-
if (this._kernel.permissionRegistry) {
|
|
418
|
-
this._kernel.permissionRegistry.registerEntity('users', {
|
|
419
|
-
module: (this._module?.constructor as { name?: string })?.name || 'unknown',
|
|
420
|
-
})
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
return this
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Register routes for this module
|
|
428
|
-
*
|
|
429
|
-
* Delegates to moduleRegistry.addRoutes with prefix application.
|
|
430
|
-
*
|
|
431
|
-
* @param basePath - Base path prefix (e.g., 'users')
|
|
432
|
-
* @param routes - Route definitions with relative paths
|
|
433
|
-
* @param opts - Route options
|
|
434
|
-
*
|
|
435
|
-
* @example
|
|
436
|
-
* ctx.routes('users', [
|
|
437
|
-
* { path: '', name: 'users', component: UserList },
|
|
438
|
-
* { path: ':id', name: 'users-edit', component: UserEdit }
|
|
439
|
-
* ], { entity: 'users' })
|
|
440
|
-
*/
|
|
441
|
-
routes(basePath: string, routes: RouteRecordRaw[], opts: RouteOptions = {}): this {
|
|
442
|
-
registry.addRoutes(basePath, routes, opts)
|
|
443
|
-
return this
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* Add a navigation item to a section
|
|
448
|
-
*
|
|
449
|
-
* @param item - Navigation item config
|
|
450
|
-
*
|
|
451
|
-
* @example
|
|
452
|
-
* ctx.navItem({ section: 'Admin', route: 'users', icon: 'pi pi-users', label: 'Users' })
|
|
453
|
-
*/
|
|
454
|
-
navItem(item: NavItem): this {
|
|
455
|
-
registry.addNavItem(item)
|
|
456
|
-
return this
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Add route family mapping for active state detection
|
|
461
|
-
*
|
|
462
|
-
* @param base - Parent route name (e.g., 'users')
|
|
463
|
-
* @param prefixes - Child route prefixes (e.g., ['users-', 'user-'])
|
|
464
|
-
*
|
|
465
|
-
* @example
|
|
466
|
-
* ctx.routeFamily('users', ['users-', 'user-'])
|
|
467
|
-
*/
|
|
468
|
-
routeFamily(base: string, prefixes: string[]): this {
|
|
469
|
-
registry.addRouteFamily(base, prefixes)
|
|
470
|
-
return this
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Register standard CRUD routes with naming conventions
|
|
475
|
-
*
|
|
476
|
-
* Generates routes following qdadm conventions:
|
|
477
|
-
* - Entity 'books' → route prefix 'book'
|
|
478
|
-
* - List: /books → name 'book'
|
|
479
|
-
* - Show: /books/:id → name 'book-show' (optional)
|
|
480
|
-
* - Create: /books/create → name 'book-create'
|
|
481
|
-
* - Edit: /books/:id/edit → name 'book-edit'
|
|
482
|
-
*
|
|
483
|
-
* Also registers route family and optional nav item.
|
|
484
|
-
*
|
|
485
|
-
* @param entity - Entity name (plural, e.g., 'books', 'users')
|
|
486
|
-
* @param pages - Page components (lazy imports)
|
|
487
|
-
* @param options - Additional options
|
|
488
|
-
*
|
|
489
|
-
* @example
|
|
490
|
-
* // Single form pattern (recommended)
|
|
491
|
-
* ctx.crud('books', {
|
|
492
|
-
* list: () => import('./pages/BookList.vue'),
|
|
493
|
-
* form: () => import('./pages/BookForm.vue')
|
|
494
|
-
* }, {
|
|
495
|
-
* nav: { section: 'Library', icon: 'pi pi-book' }
|
|
496
|
-
* })
|
|
497
|
-
*/
|
|
498
|
-
crud(entity: string, pages: CrudPages, options: CrudOptions = {}): this {
|
|
499
|
-
// Entity name is always used for permission binding
|
|
500
|
-
// Manager may not be registered yet (child entity before parent module loads)
|
|
501
|
-
const entityBinding = entity
|
|
502
|
-
const manager = this._kernel.orchestrator?.isRegistered(entity)
|
|
503
|
-
? this._kernel.orchestrator.get(entity)
|
|
504
|
-
: null
|
|
505
|
-
const idParam = manager?.idField || 'id'
|
|
506
|
-
|
|
507
|
-
// Handle parent route configuration
|
|
508
|
-
const urlSegment = options.pathSegment || this._toKebab(entity)
|
|
509
|
-
let basePath = urlSegment
|
|
510
|
-
let parentConfig: ParentConfig | null = null
|
|
511
|
-
let parentRoutePrefix: string | null = null
|
|
512
|
-
|
|
513
|
-
if (options.parentRoute) {
|
|
514
|
-
// Find parent route info from registered routes
|
|
515
|
-
const parentRouteName = options.parentRoute
|
|
516
|
-
const allRoutes = getRoutes()
|
|
517
|
-
const parentRoute = allRoutes.find((r) => r.name === parentRouteName)
|
|
518
|
-
|
|
519
|
-
if (parentRoute) {
|
|
520
|
-
// Get parent entity from route meta
|
|
521
|
-
const parentEntityName = parentRoute.meta?.entity
|
|
522
|
-
const parentManager = parentEntityName
|
|
523
|
-
? this._kernel.orchestrator?.get(parentEntityName)
|
|
524
|
-
: null
|
|
525
|
-
const parentIdParam = parentManager?.idField || 'id'
|
|
526
|
-
|
|
527
|
-
// Build base path: parentPath/:parentId/entity
|
|
528
|
-
// e.g., books/:bookId/loans
|
|
529
|
-
const parentBasePath =
|
|
530
|
-
parentRoute.path.replace(/\/(create|:.*)?$/, '') || parentEntityName
|
|
531
|
-
basePath = `${parentBasePath}/:${parentIdParam}/${urlSegment}`
|
|
532
|
-
|
|
533
|
-
// Build parent config for route meta (only if parent entity is defined)
|
|
534
|
-
if (parentEntityName) {
|
|
535
|
-
parentConfig = {
|
|
536
|
-
entity: parentEntityName,
|
|
537
|
-
param: parentIdParam,
|
|
538
|
-
foreignKey:
|
|
539
|
-
options.foreignKey || `${this._singularize(parentEntityName)}_id`,
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// Store parent route prefix for derived naming
|
|
544
|
-
parentRoutePrefix = parentRouteName
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// Derive route prefix
|
|
549
|
-
// - With parent: 'book' + '-loan' → 'book-loan'
|
|
550
|
-
// - Without parent: 'books' → 'book'
|
|
551
|
-
const routePrefix =
|
|
552
|
-
options.routePrefix ||
|
|
553
|
-
(parentRoutePrefix
|
|
554
|
-
? `${parentRoutePrefix}-${this._singularize(entity)}`
|
|
555
|
-
: this._singularize(entity))
|
|
556
|
-
|
|
557
|
-
// Build routes array
|
|
558
|
-
const routes: RouteRecordRaw[] = []
|
|
559
|
-
|
|
560
|
-
// List route
|
|
561
|
-
if (pages.list) {
|
|
562
|
-
routes.push({
|
|
563
|
-
path: '',
|
|
564
|
-
name: routePrefix,
|
|
565
|
-
component: pages.list,
|
|
566
|
-
meta: { layout: 'list' },
|
|
567
|
-
})
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Show route (read-only detail view)
|
|
571
|
-
if (pages.show) {
|
|
572
|
-
routes.push({
|
|
573
|
-
path: `:${idParam}`,
|
|
574
|
-
name: `${routePrefix}-show`,
|
|
575
|
-
component: pages.show,
|
|
576
|
-
meta: { layout: 'show' },
|
|
577
|
-
})
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// Form routes - single form or separate create/edit
|
|
581
|
-
if (pages.form) {
|
|
582
|
-
// Single form pattern (recommended)
|
|
583
|
-
routes.push({
|
|
584
|
-
path: 'create',
|
|
585
|
-
name: `${routePrefix}-create`,
|
|
586
|
-
component: pages.form,
|
|
587
|
-
})
|
|
588
|
-
routes.push({
|
|
589
|
-
path: `:${idParam}/edit`,
|
|
590
|
-
name: `${routePrefix}-edit`,
|
|
591
|
-
component: pages.form,
|
|
592
|
-
})
|
|
593
|
-
} else {
|
|
594
|
-
// Separate create/edit pages
|
|
595
|
-
if (pages.create) {
|
|
596
|
-
routes.push({
|
|
597
|
-
path: 'create',
|
|
598
|
-
name: `${routePrefix}-create`,
|
|
599
|
-
component: pages.create,
|
|
600
|
-
})
|
|
601
|
-
}
|
|
602
|
-
if (pages.edit) {
|
|
603
|
-
routes.push({
|
|
604
|
-
path: `:${idParam}/edit`,
|
|
605
|
-
name: `${routePrefix}-edit`,
|
|
606
|
-
component: pages.edit,
|
|
607
|
-
})
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// Build route options
|
|
612
|
-
const routeOpts: RouteOptions = {}
|
|
613
|
-
// Set entity if:
|
|
614
|
-
// 1. Entity is registered (manager exists), OR
|
|
615
|
-
// 2. This is a child route (parentConfig) - needs entity binding for permission checks
|
|
616
|
-
if (manager || parentConfig) {
|
|
617
|
-
routeOpts.entity = entityBinding
|
|
618
|
-
}
|
|
619
|
-
if (parentConfig) {
|
|
620
|
-
routeOpts.parent = parentConfig
|
|
621
|
-
}
|
|
622
|
-
if (options.label) {
|
|
623
|
-
routeOpts.label = options.label
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// Register routes
|
|
627
|
-
this.routes(basePath, routes, routeOpts)
|
|
628
|
-
|
|
629
|
-
// Register route family for active state detection
|
|
630
|
-
this.routeFamily(routePrefix, [`${routePrefix}-`])
|
|
631
|
-
|
|
632
|
-
// Register nav item if provided (typically not for child entities)
|
|
633
|
-
if (options.nav) {
|
|
634
|
-
const label = options.nav.label || this._capitalize(entity)
|
|
635
|
-
const navItem: NavItem = {
|
|
636
|
-
section: options.nav.section,
|
|
637
|
-
route: routePrefix,
|
|
638
|
-
icon: options.nav.icon,
|
|
639
|
-
label,
|
|
640
|
-
}
|
|
641
|
-
// Only set entity on nav item if registered (to avoid permission check failure)
|
|
642
|
-
// Routes always get entity binding, but nav items need it to be resolvable
|
|
643
|
-
if (manager) {
|
|
644
|
-
navItem.entity = entityBinding
|
|
645
|
-
}
|
|
646
|
-
this.navItem(navItem)
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
return this
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* Register a custom child page on an entity item
|
|
654
|
-
*
|
|
655
|
-
* Adds a non-entity page as a tab alongside entity child routes.
|
|
656
|
-
* The page appears in PageNav navlinks next to CRUD children (e.g., Loans).
|
|
657
|
-
*
|
|
658
|
-
* @param parentRouteName - Parent route name (e.g., 'book')
|
|
659
|
-
* @param pageName - Page slug used in URL and route name (e.g., 'statistics')
|
|
660
|
-
* @param options - Page options (component, label, icon, meta)
|
|
661
|
-
*
|
|
662
|
-
* @example
|
|
663
|
-
* ctx.childPage('book', 'statistics', {
|
|
664
|
-
* component: () => import('./pages/BookStatistics.vue'),
|
|
665
|
-
* label: 'Statistics',
|
|
666
|
-
* icon: 'pi pi-chart-bar'
|
|
667
|
-
* })
|
|
668
|
-
* // → Route: /books/:bookId/statistics, name: book-statistics
|
|
669
|
-
* // → Appears as tab in PageNav alongside entity child routes
|
|
670
|
-
*/
|
|
671
|
-
childPage(parentRouteName: string, pageName: string, options: ChildPageOptions): this {
|
|
672
|
-
const allRoutes = getRoutes()
|
|
673
|
-
const parentRoute = allRoutes.find((r) => r.name === parentRouteName)
|
|
674
|
-
|
|
675
|
-
if (!parentRoute) {
|
|
676
|
-
console.warn(`[qdadm] childPage: parent route '${parentRouteName}' not found`)
|
|
677
|
-
return this
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// Get parent entity info from route meta
|
|
681
|
-
const parentEntityName = parentRoute.meta?.entity as string | undefined
|
|
682
|
-
const parentManager = parentEntityName
|
|
683
|
-
? this._kernel.orchestrator?.get(parentEntityName)
|
|
684
|
-
: null
|
|
685
|
-
const parentIdParam = parentManager?.idField || 'id'
|
|
686
|
-
|
|
687
|
-
// Build path: parentBasePath/:parentId/pageName
|
|
688
|
-
const parentBasePath =
|
|
689
|
-
parentRoute.path.replace(/\/(create|:.*)?$/, '') || parentEntityName
|
|
690
|
-
const basePath = `${parentBasePath}/:${parentIdParam}/${pageName}`
|
|
691
|
-
|
|
692
|
-
// Route name: parentRouteName-pageName
|
|
693
|
-
const routeName = `${parentRouteName}-${pageName}`
|
|
694
|
-
|
|
695
|
-
// Build parent config (no foreignKey for non-entity pages)
|
|
696
|
-
const parentConfig: ParentConfig | undefined = parentEntityName
|
|
697
|
-
? { entity: parentEntityName, param: parentIdParam }
|
|
698
|
-
: undefined
|
|
699
|
-
|
|
700
|
-
// Build route options
|
|
701
|
-
const routeOpts: RouteOptions = {}
|
|
702
|
-
if (parentConfig) {
|
|
703
|
-
routeOpts.parent = parentConfig
|
|
704
|
-
}
|
|
705
|
-
if (options.label) {
|
|
706
|
-
routeOpts.label = options.label
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
// Register route with layout='page'
|
|
710
|
-
this.routes(basePath, [
|
|
711
|
-
{
|
|
712
|
-
path: '',
|
|
713
|
-
name: routeName,
|
|
714
|
-
component: options.component,
|
|
715
|
-
meta: {
|
|
716
|
-
layout: 'page',
|
|
717
|
-
...(options.icon && { icon: options.icon }),
|
|
718
|
-
...options.meta,
|
|
719
|
-
},
|
|
720
|
-
},
|
|
721
|
-
], routeOpts)
|
|
722
|
-
|
|
723
|
-
return this
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
/**
|
|
727
|
-
* Singularize a plural word (simple English rules)
|
|
728
|
-
*/
|
|
729
|
-
private _singularize(plural: string): string {
|
|
730
|
-
if (plural.endsWith('ies')) return plural.slice(0, -3) + 'y'
|
|
731
|
-
if (
|
|
732
|
-
plural.endsWith('ses') ||
|
|
733
|
-
plural.endsWith('xes') ||
|
|
734
|
-
plural.endsWith('zes')
|
|
735
|
-
) {
|
|
736
|
-
return plural.slice(0, -2)
|
|
737
|
-
}
|
|
738
|
-
if (plural.endsWith('s') && !plural.endsWith('ss')) return plural.slice(0, -1)
|
|
739
|
-
return plural
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
/**
|
|
743
|
-
* Convert camelCase to kebab-case (e.g. 'botTasks' → 'bot-tasks')
|
|
744
|
-
*/
|
|
745
|
-
private _toKebab(str: string): string {
|
|
746
|
-
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
/**
|
|
750
|
-
* Capitalize first letter
|
|
751
|
-
*/
|
|
752
|
-
private _capitalize(str: string): string {
|
|
753
|
-
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* Define a zone for extensible UI composition
|
|
758
|
-
*
|
|
759
|
-
* @param name - Zone name (e.g., 'users-list-header')
|
|
760
|
-
* @param options - Zone options
|
|
761
|
-
*
|
|
762
|
-
* @example
|
|
763
|
-
* ctx.zone('users-list-header')
|
|
764
|
-
* ctx.zone('users-sidebar', { default: DefaultSidebar })
|
|
765
|
-
*/
|
|
766
|
-
zone(name: string, options: ZoneOptions = {}): this {
|
|
767
|
-
if (this._kernel.zoneRegistry) {
|
|
768
|
-
this._kernel.zoneRegistry.defineZone(name, options)
|
|
769
|
-
}
|
|
770
|
-
return this
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
/**
|
|
774
|
-
* Register a block in a zone
|
|
775
|
-
*
|
|
776
|
-
* @param zoneName - Target zone name
|
|
777
|
-
* @param config - Block configuration
|
|
778
|
-
*
|
|
779
|
-
* @example
|
|
780
|
-
* ctx.block('users-list-header', { component: UserStats, weight: 10, id: 'user-stats' })
|
|
781
|
-
*/
|
|
782
|
-
block(zoneName: string, config: BlockConfig): this {
|
|
783
|
-
if (this._kernel.zoneRegistry) {
|
|
784
|
-
this._kernel.zoneRegistry.registerBlock(zoneName, config)
|
|
785
|
-
}
|
|
786
|
-
return this
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
/**
|
|
790
|
-
* Provide a value to Vue's dependency injection system
|
|
791
|
-
*
|
|
792
|
-
* @param key - Injection key
|
|
793
|
-
* @param value - Value to provide
|
|
794
|
-
*
|
|
795
|
-
* @example
|
|
796
|
-
* ctx.provide('myService', new MyService())
|
|
797
|
-
*/
|
|
798
|
-
provide(key: string | symbol, value: unknown): this {
|
|
799
|
-
if (this._kernel.vueApp) {
|
|
800
|
-
this._kernel.vueApp.provide(key, value)
|
|
801
|
-
} else {
|
|
802
|
-
// Store for later application when vueApp is created
|
|
803
|
-
this._kernel._pendingProvides.set(key, value)
|
|
804
|
-
}
|
|
805
|
-
return this
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
/**
|
|
809
|
-
* Register a global Vue component
|
|
810
|
-
*
|
|
811
|
-
* @param name - Component name
|
|
812
|
-
* @param component - Vue component
|
|
813
|
-
*
|
|
814
|
-
* @example
|
|
815
|
-
* ctx.component('MyGlobalComponent', MyComponent)
|
|
816
|
-
*/
|
|
817
|
-
component(name: string, component: Component): this {
|
|
818
|
-
if (this._kernel.vueApp) {
|
|
819
|
-
this._kernel.vueApp.component(name, component)
|
|
820
|
-
} else {
|
|
821
|
-
// Store for later registration when vueApp is created
|
|
822
|
-
this._kernel._pendingComponents.set(name, component)
|
|
823
|
-
}
|
|
824
|
-
return this
|
|
188
|
+
/** I18n subsystem */
|
|
189
|
+
get i18n(): I18n | null {
|
|
190
|
+
return this._kernel.i18nInstance ?? null
|
|
825
191
|
}
|
|
192
|
+
}
|
|
826
193
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
* @param signal - Signal name (supports wildcards via SignalBus)
|
|
834
|
-
* @param handler - Handler function (event, ctx) => void
|
|
835
|
-
* @param options - Listener options
|
|
836
|
-
*
|
|
837
|
-
* @example
|
|
838
|
-
* ctx.on('users:created', (event) => console.log('User created:', event.data))
|
|
839
|
-
* ctx.on('entity:*', (event) => console.log('Entity event:', event))
|
|
840
|
-
*/
|
|
841
|
-
on(signal: string, handler: SignalHandler, options: ListenerOptions = {}): this {
|
|
842
|
-
if (this._kernel.signals) {
|
|
843
|
-
const cleanup = this._kernel.signals.on(signal, handler, options)
|
|
844
|
-
// Register cleanup with module for automatic unsubscribe on disconnect
|
|
845
|
-
if (this._module && typeof this._module._addSignalCleanup === 'function') {
|
|
846
|
-
this._module._addSignalCleanup(cleanup)
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
return this
|
|
850
|
-
}
|
|
194
|
+
// Declare prototype-patched methods (declaration merging)
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
196
|
+
export interface KernelContext {
|
|
197
|
+
// Entities (KernelContext.entities.ts)
|
|
198
|
+
entity(name: string, config: string | Record<string, unknown> | EntityManager): this
|
|
199
|
+
userEntity(options: UserEntityOptions): this
|
|
851
200
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
*
|
|
859
|
-
* @example
|
|
860
|
-
* ctx.hook('users:presave', (context) => {
|
|
861
|
-
* context.data.updated_at = new Date().toISOString()
|
|
862
|
-
* return context
|
|
863
|
-
* })
|
|
864
|
-
*/
|
|
865
|
-
hook(hookName: string, handler: HookHandler, options: Record<string, unknown> = {}): this {
|
|
866
|
-
if (this._kernel.hookRegistry) {
|
|
867
|
-
this._kernel.hookRegistry.register(hookName, handler, options)
|
|
868
|
-
}
|
|
869
|
-
return this
|
|
870
|
-
}
|
|
201
|
+
// Routing (KernelContext.routing.ts)
|
|
202
|
+
routes(basePath: string, routes: RouteRecordRaw[], opts?: RouteOptions): this
|
|
203
|
+
navItem(item: NavItem): this
|
|
204
|
+
routeFamily(base: string, prefixes: string[]): this
|
|
205
|
+
crud(entity: string, pages: CrudPages, options?: CrudOptions): this
|
|
206
|
+
childPage(parentRouteName: string, pageName: string, options: ChildPageOptions): this
|
|
871
207
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
*
|
|
878
|
-
* @example
|
|
879
|
-
* ctx.deferred('users:warmup', async () => {
|
|
880
|
-
* await ctx.orchestrator.get('users').warmup()
|
|
881
|
-
* })
|
|
882
|
-
*/
|
|
883
|
-
defer(name: string, factory: DeferredFactory): this {
|
|
884
|
-
if (this._kernel.deferred) {
|
|
885
|
-
this._kernel.deferred.queue(name, factory)
|
|
886
|
-
}
|
|
887
|
-
return this
|
|
888
|
-
}
|
|
208
|
+
// Zones / Vue (KernelContext.zones.ts)
|
|
209
|
+
zone(name: string, options?: ZoneOptions): this
|
|
210
|
+
block(zoneName: string, config: BlockConfig): this
|
|
211
|
+
provide(key: string | symbol, value: unknown): this
|
|
212
|
+
component(name: string, component: Component): this
|
|
889
213
|
|
|
890
|
-
//
|
|
891
|
-
|
|
892
|
-
|
|
214
|
+
// Events (KernelContext.events.ts)
|
|
215
|
+
on(signal: string, handler: SignalHandler, options?: ListenerOptions): this
|
|
216
|
+
hook(hookName: string, handler: HookHandler, options?: Record<string, unknown>): this
|
|
217
|
+
defer(name: string, factory: DeferredFactory): this
|
|
893
218
|
|
|
894
|
-
|
|
895
|
-
* Register permissions for this module
|
|
896
|
-
*
|
|
897
|
-
* Permissions are namespaced and collected in the central PermissionRegistry.
|
|
898
|
-
* Use this to declare what permissions your module provides.
|
|
899
|
-
*
|
|
900
|
-
* Permission format: namespace:action
|
|
901
|
-
* - For entity CRUD: automatically prefixed with 'entity:'
|
|
902
|
-
* - For system features: use any namespace (auth:, admin:, feature:, etc.)
|
|
903
|
-
*
|
|
904
|
-
* @param namespace - Permission namespace (e.g., 'books', 'auth', 'admin:config')
|
|
905
|
-
* @param permissions - Permission definitions
|
|
906
|
-
* @param options - Permission options
|
|
907
|
-
*
|
|
908
|
-
* @example
|
|
909
|
-
* // Entity custom permissions (auto-prefixed with 'entity:')
|
|
910
|
-
* ctx.permissions('books', {
|
|
911
|
-
* checkout: 'Checkout a book',
|
|
912
|
-
* reserve: { label: 'Reserve', description: 'Reserve a book for later' }
|
|
913
|
-
* }, { isEntity: true })
|
|
914
|
-
* // → entity:books:checkout, entity:books:reserve
|
|
915
|
-
*/
|
|
219
|
+
// Permissions (KernelContext.permissions.ts)
|
|
916
220
|
permissions(
|
|
917
221
|
namespace: string,
|
|
918
222
|
permissions: Record<string, string | PermissionMeta>,
|
|
919
|
-
options
|
|
920
|
-
): this
|
|
921
|
-
if (this._kernel.permissionRegistry) {
|
|
922
|
-
this._kernel.permissionRegistry.register(namespace, permissions, {
|
|
923
|
-
...options,
|
|
924
|
-
module: (this._module?.constructor as { name?: string })?.name || 'unknown',
|
|
925
|
-
})
|
|
926
|
-
}
|
|
927
|
-
return this
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
/**
|
|
931
|
-
* Register entity permissions (convenience method)
|
|
932
|
-
*
|
|
933
|
-
* Shorthand for registering entity-namespaced permissions.
|
|
934
|
-
* Equivalent to: ctx.permissions(entity, perms, { isEntity: true })
|
|
935
|
-
*
|
|
936
|
-
* @param entity - Entity name
|
|
937
|
-
* @param permissions - Permission definitions
|
|
938
|
-
*
|
|
939
|
-
* @example
|
|
940
|
-
* ctx.entityPermissions('books', {
|
|
941
|
-
* checkout: 'Checkout a book'
|
|
942
|
-
* })
|
|
943
|
-
* // → entity:books:checkout
|
|
944
|
-
*/
|
|
223
|
+
options?: PermissionOptions
|
|
224
|
+
): this
|
|
945
225
|
entityPermissions(
|
|
946
226
|
entity: string,
|
|
947
227
|
permissions: Record<string, string | PermissionMeta>
|
|
948
|
-
): this
|
|
949
|
-
|
|
950
|
-
|
|
228
|
+
): this
|
|
229
|
+
|
|
230
|
+
// i18n (KernelContext.i18n.ts)
|
|
231
|
+
messages(locale: string, bundle: MessagesBundle): this
|
|
232
|
+
aliases(patterns: AliasPattern[]): this
|
|
233
|
+
messagesProvider(provider: TranslationProvider): this
|
|
951
234
|
}
|
|
952
235
|
|
|
236
|
+
// Apply prototype patches
|
|
237
|
+
applyEntityMethods(KernelContext)
|
|
238
|
+
applyRoutingMethods(KernelContext)
|
|
239
|
+
applyZoneMethods(KernelContext)
|
|
240
|
+
applyEventMethods(KernelContext)
|
|
241
|
+
applyPermissionMethods(KernelContext)
|
|
242
|
+
applyI18nMethods(KernelContext)
|
|
243
|
+
|
|
953
244
|
/**
|
|
954
|
-
* Factory function to create a KernelContext instance
|
|
245
|
+
* Factory function to create a KernelContext instance.
|
|
955
246
|
*/
|
|
956
247
|
export function createKernelContext(
|
|
957
248
|
kernel: KernelInterface,
|