@quazardous/qdadm 1.19.3

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 (291) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/package.json +101 -0
  4. package/src/assets/logo.svg +6 -0
  5. package/src/auth/SessionAuthAdapter.ts +459 -0
  6. package/src/auth/index.ts +15 -0
  7. package/src/chain/ActiveStack.ts +154 -0
  8. package/src/chain/StackHydrator.ts +292 -0
  9. package/src/chain/index.ts +25 -0
  10. package/src/chain/useActiveStack.ts +120 -0
  11. package/src/chain/useStackHydrator.ts +155 -0
  12. package/src/components/BoolCell.vue +34 -0
  13. package/src/components/InfoBanner.vue +73 -0
  14. package/src/components/QdadmRoot.vue +52 -0
  15. package/src/components/SeverityTag.vue +86 -0
  16. package/src/components/dialogs/BulkStatusDialog.vue +60 -0
  17. package/src/components/dialogs/MultiStepDialog.vue +331 -0
  18. package/src/components/dialogs/SimpleDialog.vue +122 -0
  19. package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
  20. package/src/components/display/BannerZone.vue +65 -0
  21. package/src/components/display/CardsGrid.vue +72 -0
  22. package/src/components/display/CopyableId.vue +84 -0
  23. package/src/components/display/EmptyState.vue +62 -0
  24. package/src/components/display/IntensityBar.vue +190 -0
  25. package/src/components/display/RichCardsGrid.vue +101 -0
  26. package/src/components/edit/FormActions.vue +78 -0
  27. package/src/components/edit/FormField.vue +82 -0
  28. package/src/components/edit/FormInput.vue +158 -0
  29. package/src/components/edit/FormPage.vue +260 -0
  30. package/src/components/edit/FormTab.vue +217 -0
  31. package/src/components/edit/FormTabs.vue +120 -0
  32. package/src/components/edit/LookupField.vue +406 -0
  33. package/src/components/edit/LookupPickerDialog.vue +230 -0
  34. package/src/components/editors/JsonEditorFoldable.vue +470 -0
  35. package/src/components/editors/JsonStructuredField.vue +264 -0
  36. package/src/components/editors/JsonViewer.vue +51 -0
  37. package/src/components/editors/KeyValueEditor.vue +314 -0
  38. package/src/components/editors/LanguageEditor.vue +259 -0
  39. package/src/components/editors/PermissionEditor.vue +558 -0
  40. package/src/components/editors/ScopeEditor.vue +372 -0
  41. package/src/components/editors/VanillaJsonEditor.vue +183 -0
  42. package/src/components/index.ts +83 -0
  43. package/src/components/item/FieldGroups.vue +392 -0
  44. package/src/components/layout/AppLayout.vue +1032 -0
  45. package/src/components/layout/BaseLayout.vue +190 -0
  46. package/src/components/layout/Breadcrumb.vue +55 -0
  47. package/src/components/layout/DashboardLayout.vue +104 -0
  48. package/src/components/layout/FormLayout.vue +256 -0
  49. package/src/components/layout/ListLayout.vue +278 -0
  50. package/src/components/layout/PageHeader.vue +156 -0
  51. package/src/components/layout/PageLayout.vue +126 -0
  52. package/src/components/layout/PageNav.vue +192 -0
  53. package/src/components/layout/SidebarBox.vue +87 -0
  54. package/src/components/layout/Zone.vue +182 -0
  55. package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
  56. package/src/components/layout/defaults/DefaultFooter.vue +89 -0
  57. package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
  58. package/src/components/layout/defaults/DefaultHeader.vue +78 -0
  59. package/src/components/layout/defaults/DefaultMenu.vue +188 -0
  60. package/src/components/layout/defaults/DefaultPagination.vue +75 -0
  61. package/src/components/layout/defaults/DefaultTable.vue +137 -0
  62. package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
  63. package/src/components/layout/defaults/index.ts +16 -0
  64. package/src/components/lists/ActionButtons.vue +49 -0
  65. package/src/components/lists/ActionColumn.vue +40 -0
  66. package/src/components/lists/FilterBar.vue +54 -0
  67. package/src/components/lists/ListPage.vue +467 -0
  68. package/src/components/pages/LoginPage.vue +334 -0
  69. package/src/components/pages/NotFoundPage.vue +98 -0
  70. package/src/components/show/ShowDisplay.vue +343 -0
  71. package/src/components/show/ShowField.vue +67 -0
  72. package/src/components/show/ShowPage.vue +431 -0
  73. package/src/composables/index.ts +155 -0
  74. package/src/composables/useApp.ts +67 -0
  75. package/src/composables/useAuth.ts +98 -0
  76. package/src/composables/useBareForm.ts +319 -0
  77. package/src/composables/useBreadcrumb.ts +252 -0
  78. package/src/composables/useChildPage.ts +113 -0
  79. package/src/composables/useCurrentEntity.ts +60 -0
  80. package/src/composables/useDeferred.ts +94 -0
  81. package/src/composables/useDirtyState.ts +138 -0
  82. package/src/composables/useEntityItemFormPage.ts +942 -0
  83. package/src/composables/useEntityItemFormPage.types.ts +451 -0
  84. package/src/composables/useEntityItemPage.ts +438 -0
  85. package/src/composables/useEntityItemShowPage.ts +722 -0
  86. package/src/composables/useFieldManager.ts +692 -0
  87. package/src/composables/useForm.ts +544 -0
  88. package/src/composables/useGuardStore.ts +53 -0
  89. package/src/composables/useHooks.ts +56 -0
  90. package/src/composables/useInfoBanner.ts +217 -0
  91. package/src/composables/useJsonSyntax.ts +122 -0
  92. package/src/composables/useLayoutResolver.ts +329 -0
  93. package/src/composables/useListPage.ts +1535 -0
  94. package/src/composables/useListPage.types.ts +437 -0
  95. package/src/composables/useListPage.utils.ts +101 -0
  96. package/src/composables/useNavContext.ts +439 -0
  97. package/src/composables/useNavigation.ts +216 -0
  98. package/src/composables/useOptionsLookup.ts +323 -0
  99. package/src/composables/usePageTitle.ts +84 -0
  100. package/src/composables/useSSEBridge.ts +142 -0
  101. package/src/composables/useSemanticBreadcrumb.ts +222 -0
  102. package/src/composables/useSidebarState.ts +24 -0
  103. package/src/composables/useSignals.ts +52 -0
  104. package/src/composables/useUnsavedChangesGuard.ts +155 -0
  105. package/src/composables/useUserImpersonator.ts +317 -0
  106. package/src/composables/useZoneRegistry.ts +252 -0
  107. package/src/core/bundles.ts +558 -0
  108. package/src/core/decorator.ts +420 -0
  109. package/src/core/extension.ts +470 -0
  110. package/src/core/index.ts +59 -0
  111. package/src/deferred/DeferredRegistry.ts +353 -0
  112. package/src/deferred/index.ts +15 -0
  113. package/src/editors/index.ts +12 -0
  114. package/src/entity/EntityManager.cache.ts +530 -0
  115. package/src/entity/EntityManager.crud.ts +615 -0
  116. package/src/entity/EntityManager.interface.ts +78 -0
  117. package/src/entity/EntityManager.query.ts +221 -0
  118. package/src/entity/EntityManager.relations.ts +151 -0
  119. package/src/entity/EntityManager.ts +944 -0
  120. package/src/entity/EntityManager.types.ts +288 -0
  121. package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
  122. package/src/entity/auth/EntityAuthAdapter.ts +119 -0
  123. package/src/entity/auth/PermissiveAdapter.ts +43 -0
  124. package/src/entity/auth/RoleHierarchy.ts +128 -0
  125. package/src/entity/auth/SecurityChecker.ts +202 -0
  126. package/src/entity/auth/factory.ts +203 -0
  127. package/src/entity/auth/index.ts +47 -0
  128. package/src/entity/factory.test.js +189 -0
  129. package/src/entity/factory.ts +146 -0
  130. package/src/entity/index.ts +49 -0
  131. package/src/entity/storage/ApiStorage.ts +228 -0
  132. package/src/entity/storage/IStorage.ts +100 -0
  133. package/src/entity/storage/LocalStorage.ts +200 -0
  134. package/src/entity/storage/MemoryStorage.ts +184 -0
  135. package/src/entity/storage/MockApiStorage.ts +318 -0
  136. package/src/entity/storage/SdkStorage.ts +377 -0
  137. package/src/entity/storage/factory.test.js +159 -0
  138. package/src/entity/storage/factory.ts +209 -0
  139. package/src/entity/storage/index.ts +75 -0
  140. package/src/gen/FieldMapper.ts +122 -0
  141. package/src/gen/StorageProfileFactory.ts +93 -0
  142. package/src/gen/connectors/BaseConnector.ts +150 -0
  143. package/src/gen/connectors/ManualConnector.test.js +499 -0
  144. package/src/gen/connectors/ManualConnector.ts +414 -0
  145. package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
  146. package/src/gen/connectors/OpenAPIConnector.ts +693 -0
  147. package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
  148. package/src/gen/connectors/index.ts +27 -0
  149. package/src/gen/createManagers.ts +245 -0
  150. package/src/gen/decorators.ts +134 -0
  151. package/src/gen/generateManagers.test.js +540 -0
  152. package/src/gen/generateManagers.ts +398 -0
  153. package/src/gen/index.ts +66 -0
  154. package/src/gen/schema.ts +248 -0
  155. package/src/gen/vite-plugin.ts +114 -0
  156. package/src/generated/managers/testManager.js +45 -0
  157. package/src/generated/managers/testManager.ts +46 -0
  158. package/src/hooks/HookRegistry.ts +16 -0
  159. package/src/hooks/index.ts +15 -0
  160. package/src/i18n/I18n.ts +344 -0
  161. package/src/i18n/IncrementalDomainProvider.ts +153 -0
  162. package/src/i18n/InlineTranslationProvider.ts +4 -0
  163. package/src/i18n/LazyTranslationProvider.ts +102 -0
  164. package/src/i18n/MessagesRegistry.ts +4 -0
  165. package/src/i18n/Resolver.ts +4 -0
  166. package/src/i18n/__tests__/I18n.test.ts +169 -0
  167. package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
  168. package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
  169. package/src/i18n/__tests__/Resolver.test.ts +271 -0
  170. package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
  171. package/src/i18n/defaults/core.en.yml +55 -0
  172. package/src/i18n/defaults/core.fr.yml +55 -0
  173. package/src/i18n/index.ts +55 -0
  174. package/src/i18n/loaders/raw-modules.d.ts +15 -0
  175. package/src/i18n/loaders/yaml.ts +35 -0
  176. package/src/i18n/strategies.ts +4 -0
  177. package/src/i18n/types.ts +34 -0
  178. package/src/i18n/useI18n.ts +34 -0
  179. package/src/index.ts +453 -0
  180. package/src/kernel/EventRouter.ts +23 -0
  181. package/src/kernel/Kernel.api.ts +79 -0
  182. package/src/kernel/Kernel.auth.ts +120 -0
  183. package/src/kernel/Kernel.i18n.ts +29 -0
  184. package/src/kernel/Kernel.modules.ts +170 -0
  185. package/src/kernel/Kernel.registries.ts +237 -0
  186. package/src/kernel/Kernel.routing.ts +334 -0
  187. package/src/kernel/Kernel.ts +460 -0
  188. package/src/kernel/Kernel.types.ts +263 -0
  189. package/src/kernel/Kernel.vue.ts +304 -0
  190. package/src/kernel/KernelContext.entities.ts +80 -0
  191. package/src/kernel/KernelContext.events.ts +57 -0
  192. package/src/kernel/KernelContext.i18n.ts +37 -0
  193. package/src/kernel/KernelContext.permissions.ts +38 -0
  194. package/src/kernel/KernelContext.routing.ts +280 -0
  195. package/src/kernel/KernelContext.ts +252 -0
  196. package/src/kernel/KernelContext.types.ts +173 -0
  197. package/src/kernel/KernelContext.zones.ts +54 -0
  198. package/src/kernel/Module.ts +169 -0
  199. package/src/kernel/ModuleLoader.ts +645 -0
  200. package/src/kernel/SSEBridge.ts +11 -0
  201. package/src/kernel/SignalBus.ts +48 -0
  202. package/src/kernel/index.ts +56 -0
  203. package/src/module/index.ts +33 -0
  204. package/src/module/moduleRegistry.ts +492 -0
  205. package/src/modules/debug/AuthCollector.ts +501 -0
  206. package/src/modules/debug/Collector.ts +20 -0
  207. package/src/modules/debug/DebugBridge.ts +14 -0
  208. package/src/modules/debug/DebugModule.ts +220 -0
  209. package/src/modules/debug/EntitiesCollector.ts +839 -0
  210. package/src/modules/debug/ErrorCollector.ts +4 -0
  211. package/src/modules/debug/I18nCollector.ts +9 -0
  212. package/src/modules/debug/LocalStorageAdapter.ts +7 -0
  213. package/src/modules/debug/RouterCollector.ts +449 -0
  214. package/src/modules/debug/SignalCollector.ts +4 -0
  215. package/src/modules/debug/ToastCollector.ts +4 -0
  216. package/src/modules/debug/ZonesCollector.ts +447 -0
  217. package/src/modules/debug/components/DebugBar.vue +31 -0
  218. package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
  219. package/src/modules/debug/components/index.ts +9 -0
  220. package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
  221. package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
  222. package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
  223. package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
  224. package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
  225. package/src/modules/debug/components/panels/index.ts +16 -0
  226. package/src/modules/debug/index.ts +58 -0
  227. package/src/modules/debug/styles.scss +2671 -0
  228. package/src/notifications/NotificationBadge.vue +35 -0
  229. package/src/notifications/NotificationListener.vue +71 -0
  230. package/src/notifications/NotificationModule.ts +76 -0
  231. package/src/notifications/NotificationPanel.vue +478 -0
  232. package/src/notifications/NotificationStore.ts +288 -0
  233. package/src/notifications/index.ts +42 -0
  234. package/src/notifications/styles.scss +32 -0
  235. package/src/orchestrator/Orchestrator.ts +427 -0
  236. package/src/orchestrator/index.ts +8 -0
  237. package/src/orchestrator/useOrchestrator.ts +80 -0
  238. package/src/plugin.ts +251 -0
  239. package/src/query/FilterQuery.ts +306 -0
  240. package/src/query/QueryExecutor.ts +341 -0
  241. package/src/query/index.ts +16 -0
  242. package/src/security/EntityRolesProvider.ts +385 -0
  243. package/src/security/PermissionMatcher.ts +152 -0
  244. package/src/security/PermissionRegistry.ts +273 -0
  245. package/src/security/PersistableRolesProvider.ts +584 -0
  246. package/src/security/RolesManager.ts +136 -0
  247. package/src/security/RolesProvider.ts +167 -0
  248. package/src/security/RolesProviderStorage.ts +211 -0
  249. package/src/security/SecurityModule.ts +94 -0
  250. package/src/security/StaticRolesProvider.ts +117 -0
  251. package/src/security/UsersManager.ts +163 -0
  252. package/src/security/index.ts +77 -0
  253. package/src/security/pages/RoleForm.vue +276 -0
  254. package/src/security/pages/RoleList.vue +123 -0
  255. package/src/styles/_alerts.scss +95 -0
  256. package/src/styles/_buttons.scss +391 -0
  257. package/src/styles/_cards.scss +241 -0
  258. package/src/styles/_code.scss +78 -0
  259. package/src/styles/_dialogs.scss +55 -0
  260. package/src/styles/_filter-bar.scss +70 -0
  261. package/src/styles/_forms.scss +434 -0
  262. package/src/styles/_helpers.scss +153 -0
  263. package/src/styles/_lists.scss +225 -0
  264. package/src/styles/_main.scss +356 -0
  265. package/src/styles/_markdown.scss +88 -0
  266. package/src/styles/_responsive.scss +255 -0
  267. package/src/styles/_show-pages.scss +186 -0
  268. package/src/styles/_sidebar.scss +380 -0
  269. package/src/styles/_states.scss +115 -0
  270. package/src/styles/_stats.scss +47 -0
  271. package/src/styles/_variables.scss +224 -0
  272. package/src/styles/index.scss +38 -0
  273. package/src/styles/theme/_components.scss +331 -0
  274. package/src/styles/theme/_index.scss +10 -0
  275. package/src/styles/theme/_tokens.scss +147 -0
  276. package/src/styles/theme/_utilities.scss +175 -0
  277. package/src/toast/ToastBridgeModule.ts +68 -0
  278. package/src/toast/ToastListener.vue +55 -0
  279. package/src/toast/index.ts +26 -0
  280. package/src/toast/useSignalToast.ts +154 -0
  281. package/src/types/index.ts +285 -0
  282. package/src/types/pluralize.d.ts +19 -0
  283. package/src/types/quarkernel.d.ts +50 -0
  284. package/src/utils/formatters.ts +174 -0
  285. package/src/utils/index.ts +35 -0
  286. package/src/utils/transformers.ts +131 -0
  287. package/src/vite/qdadmDebugPlugin.ts +401 -0
  288. package/src/vite-env.d.ts +50 -0
  289. package/src/zones/ZoneRegistry.ts +934 -0
  290. package/src/zones/index.ts +29 -0
  291. package/src/zones/zones.ts +191 -0
@@ -0,0 +1,645 @@
1
+ /**
2
+ * ModuleLoader - Duck typing module loader with dependency resolution
3
+ *
4
+ * Detects and normalizes multiple module formats:
5
+ * 1. Module instance - use directly
6
+ * 2. Module class - instantiate with new
7
+ * 3. Plain object with connect() - wrap in adapter
8
+ * 4. Plain function - wrap as legacy init({ registry, zones })
9
+ *
10
+ * Provides topological sorting based on requires + priority.
11
+ */
12
+
13
+ import { Module } from './Module'
14
+
15
+ // ─────────────────────────────────────────────────────────────────────────────
16
+ // Types
17
+ // ─────────────────────────────────────────────────────────────────────────────
18
+
19
+ /**
20
+ * Context passed to module connect() method
21
+ */
22
+ export interface ModuleContext {
23
+ routes: (basePath: string, routes: unknown[], opts?: unknown) => void
24
+ navItem: (item: unknown) => void
25
+ routeFamily: (base: string, prefixes: string[]) => void
26
+ zones: unknown
27
+ [key: string]: unknown
28
+ }
29
+
30
+ /**
31
+ * Module-like interface that all adapters implement
32
+ */
33
+ export interface ModuleLike {
34
+ name: string
35
+ requires: string[]
36
+ priority: number
37
+ enabled(ctx: ModuleContext): boolean
38
+ connect(ctx: ModuleContext): Promise<void>
39
+ disconnect?(): Promise<void>
40
+ loadStyles?(): Promise<void>
41
+ }
42
+
43
+ /**
44
+ * Plain object module definition
45
+ */
46
+ export interface ObjectModuleDefinition {
47
+ name?: string
48
+ requires?: string[]
49
+ priority?: number
50
+ enabled?: boolean | ((ctx: ModuleContext) => boolean)
51
+ connect?: (ctx: ModuleContext) => void | Promise<void>
52
+ disconnect?: () => void | Promise<void>
53
+ }
54
+
55
+ /**
56
+ * Module class constructor with static properties
57
+ */
58
+ export interface ModuleClassConstructor {
59
+ new (): ModuleLike
60
+ name: string // Function.name (class name)
61
+ moduleName?: string // Static moduleName property
62
+ requires?: string[]
63
+ priority?: number
64
+ enabled?: (ctx: ModuleContext) => boolean
65
+ prototype: {
66
+ connect?: (ctx: ModuleContext) => void | Promise<void>
67
+ enabled?: (ctx: ModuleContext) => boolean
68
+ disconnect?: () => void | Promise<void>
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Legacy init function signature
74
+ */
75
+ export interface LegacyInitApi {
76
+ registry: {
77
+ addRoutes: (basePath: string, routes: unknown[], opts?: unknown) => void
78
+ addNavItem: (item: unknown) => void
79
+ addRouteFamily: (base: string, prefixes: string[]) => void
80
+ }
81
+ zones: unknown
82
+ ctx: ModuleContext
83
+ }
84
+
85
+ export type LegacyInitFunction = (api: LegacyInitApi) => void | Promise<void>
86
+
87
+ /**
88
+ * Any valid module definition
89
+ */
90
+ export type ModuleDefinition =
91
+ | Module
92
+ | ModuleLike
93
+ | ModuleClassConstructor
94
+ | ObjectModuleDefinition
95
+ | LegacyInitFunction
96
+
97
+ // ─────────────────────────────────────────────────────────────────────────────
98
+ // Custom Errors
99
+ // ─────────────────────────────────────────────────────────────────────────────
100
+
101
+ /**
102
+ * Error thrown when a required module is not registered
103
+ */
104
+ export class ModuleNotFoundError extends Error {
105
+ moduleName: string
106
+ requiredBy: string
107
+
108
+ constructor(moduleName: string, requiredBy: string) {
109
+ super(`Module '${moduleName}' not found (required by '${requiredBy}')`)
110
+ this.name = 'ModuleNotFoundError'
111
+ this.moduleName = moduleName
112
+ this.requiredBy = requiredBy
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Error thrown when circular dependencies are detected
118
+ */
119
+ export class CircularDependencyError extends Error {
120
+ cycle: string[]
121
+
122
+ constructor(cycle: string[]) {
123
+ const cyclePath = cycle.join(' → ')
124
+ super(`Circular dependency detected: ${cyclePath}`)
125
+ this.name = 'CircularDependencyError'
126
+ this.cycle = cycle
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Error thrown when module connect() fails
132
+ */
133
+ export class ModuleLoadError extends Error {
134
+ moduleName: string
135
+ override cause: Error
136
+
137
+ constructor(moduleName: string, cause: Error) {
138
+ super(`Failed to load module '${moduleName}': ${cause.message}`)
139
+ this.name = 'ModuleLoadError'
140
+ this.moduleName = moduleName
141
+ this.cause = cause
142
+ }
143
+ }
144
+
145
+ // ─────────────────────────────────────────────────────────────────────────────
146
+ // Module Adapter for plain objects
147
+ // ─────────────────────────────────────────────────────────────────────────────
148
+
149
+ /**
150
+ * Wraps a plain object with connect() into a Module-like interface
151
+ */
152
+ class ObjectModuleAdapter implements ModuleLike {
153
+ private _def: ObjectModuleDefinition
154
+ private _ctx: ModuleContext | null = null
155
+
156
+ constructor(def: ObjectModuleDefinition) {
157
+ this._def = def
158
+ }
159
+
160
+ get name(): string {
161
+ return this._def.name || 'anonymous'
162
+ }
163
+
164
+ get requires(): string[] {
165
+ return this._def.requires || []
166
+ }
167
+
168
+ get priority(): number {
169
+ return this._def.priority ?? 0
170
+ }
171
+
172
+ enabled(ctx: ModuleContext): boolean {
173
+ if (typeof this._def.enabled === 'function') {
174
+ return this._def.enabled(ctx)
175
+ }
176
+ return this._def.enabled !== false
177
+ }
178
+
179
+ async connect(ctx: ModuleContext): Promise<void> {
180
+ this._ctx = ctx
181
+ if (typeof this._def.connect === 'function') {
182
+ await this._def.connect(ctx)
183
+ }
184
+ }
185
+
186
+ async disconnect(): Promise<void> {
187
+ if (typeof this._def.disconnect === 'function') {
188
+ await this._def.disconnect()
189
+ }
190
+ this._ctx = null
191
+ }
192
+ }
193
+
194
+ // ─────────────────────────────────────────────────────────────────────────────
195
+ // Class Adapter for non-Module classes
196
+ // ─────────────────────────────────────────────────────────────────────────────
197
+
198
+ /**
199
+ * Wraps a class with static name and connect method into a Module-like interface
200
+ */
201
+ class ClassModuleAdapter implements ModuleLike {
202
+ private _ClassDef: ModuleClassConstructor
203
+ private _instance: ModuleLike
204
+ private _ctx: ModuleContext | null = null
205
+
206
+ constructor(ClassDef: ModuleClassConstructor) {
207
+ this._ClassDef = ClassDef
208
+ this._instance = new ClassDef()
209
+ }
210
+
211
+ get name(): string {
212
+ // Check for OWN moduleName property first (not inherited from Module base class)
213
+ // This prevents inheriting 'base' from Module when subclass uses 'static name' instead
214
+ if (Object.hasOwn(this._ClassDef, 'moduleName') && this._ClassDef.moduleName) {
215
+ return this._ClassDef.moduleName
216
+ }
217
+ // Fall back to name property (could be static name = 'xxx' or Function.name)
218
+ return this._ClassDef.name || 'anonymous'
219
+ }
220
+
221
+ get requires(): string[] {
222
+ return this._ClassDef.requires || []
223
+ }
224
+
225
+ get priority(): number {
226
+ return this._ClassDef.priority ?? 0
227
+ }
228
+
229
+ enabled(ctx: ModuleContext): boolean {
230
+ if (typeof this._instance.enabled === 'function') {
231
+ return this._instance.enabled(ctx)
232
+ }
233
+ if (typeof this._ClassDef.enabled === 'function') {
234
+ return this._ClassDef.enabled(ctx)
235
+ }
236
+ return true
237
+ }
238
+
239
+ async connect(ctx: ModuleContext): Promise<void> {
240
+ this._ctx = ctx
241
+ if (typeof this._instance.connect === 'function') {
242
+ await this._instance.connect(ctx)
243
+ }
244
+ }
245
+
246
+ async disconnect(): Promise<void> {
247
+ if (typeof this._instance.disconnect === 'function') {
248
+ await this._instance.disconnect()
249
+ }
250
+ this._ctx = null
251
+ }
252
+ }
253
+
254
+ // ─────────────────────────────────────────────────────────────────────────────
255
+ // Legacy Function Adapter
256
+ // ─────────────────────────────────────────────────────────────────────────────
257
+
258
+ /**
259
+ * Wraps a legacy init function into a Module-like interface
260
+ */
261
+ class LegacyFunctionAdapter implements ModuleLike {
262
+ private _initFn: LegacyInitFunction
263
+ private _name: string
264
+
265
+ constructor(initFn: LegacyInitFunction) {
266
+ this._initFn = initFn
267
+ this._name = initFn.name || 'legacyModule'
268
+ }
269
+
270
+ get name(): string {
271
+ return this._name
272
+ }
273
+
274
+ get requires(): string[] {
275
+ return []
276
+ }
277
+
278
+ get priority(): number {
279
+ return 0
280
+ }
281
+
282
+ enabled(): boolean {
283
+ return true
284
+ }
285
+
286
+ async connect(ctx: ModuleContext): Promise<void> {
287
+ // Legacy pattern: init({ registry, zones })
288
+ // Adapt KernelContext to legacy interface
289
+ const legacyApi: LegacyInitApi = {
290
+ registry: {
291
+ addRoutes: (basePath: string, routes: unknown[], opts?: unknown) =>
292
+ ctx.routes(basePath, routes, opts),
293
+ addNavItem: (item: unknown) => ctx.navItem(item),
294
+ addRouteFamily: (base: string, prefixes: string[]) => ctx.routeFamily(base, prefixes),
295
+ },
296
+ zones: ctx.zones,
297
+ ctx,
298
+ }
299
+ await this._initFn(legacyApi)
300
+ }
301
+
302
+ async disconnect(): Promise<void> {
303
+ // Legacy functions don't support disconnect
304
+ }
305
+ }
306
+
307
+ // ─────────────────────────────────────────────────────────────────────────────
308
+ // ModuleLoader
309
+ // ─────────────────────────────────────────────────────────────────────────────
310
+
311
+ /**
312
+ * ModuleLoader - Loads and manages modules with dependency resolution
313
+ */
314
+ export class ModuleLoader {
315
+ /** Registered module definitions (before normalization) */
316
+ private _registered: Map<string, ModuleLike> = new Map()
317
+
318
+ /** Loaded module instances */
319
+ private _loaded: Map<string, ModuleLike> = new Map()
320
+
321
+ /** Load order for proper unloading */
322
+ private _loadOrder: string[] = []
323
+
324
+ /**
325
+ * Register a module (any format)
326
+ *
327
+ * Accepts:
328
+ * - Module instance (instanceof Module)
329
+ * - Module class (has static name + prototype.connect or extends Module)
330
+ * - Plain object with connect function
331
+ * - Plain function (legacy init pattern)
332
+ *
333
+ * @param moduleDef - Module definition in any format
334
+ * @returns this for chaining
335
+ *
336
+ * @example
337
+ * loader.add(new UsersModule())
338
+ * loader.add(UsersModule)
339
+ * loader.add({ name: 'simple', connect(ctx) { ... } })
340
+ * loader.add(function initLegacy({ registry }) { ... })
341
+ */
342
+ add(moduleDef: ModuleDefinition): this {
343
+ const normalized = this._normalize(moduleDef)
344
+ const name = normalized.name
345
+
346
+ if (!name || name === 'anonymous') {
347
+ throw new Error('Module must have a name (static name property, options.name, or function name)')
348
+ }
349
+
350
+ if (this._registered.has(name)) {
351
+ throw new Error(`Module '${name}' is already registered`)
352
+ }
353
+
354
+ this._registered.set(name, normalized)
355
+ return this
356
+ }
357
+
358
+ /**
359
+ * Load all registered modules in dependency order
360
+ *
361
+ * @param ctx - Context to pass to connect() (typically KernelContext-like)
362
+ * @throws ModuleNotFoundError When a required module is not registered
363
+ * @throws CircularDependencyError When circular dependencies exist
364
+ * @throws ModuleLoadError When a module's connect() fails
365
+ */
366
+ async loadAll(ctx: ModuleContext): Promise<void> {
367
+ // Get sorted modules
368
+ const sorted = this._topologicalSort()
369
+
370
+ // Load in order
371
+ for (const name of sorted) {
372
+ const module = this._registered.get(name)
373
+
374
+ if (!module) continue
375
+
376
+ // Check if enabled
377
+ if (!module.enabled(ctx)) {
378
+ continue
379
+ }
380
+
381
+ // Load styles and connect module
382
+ try {
383
+ // Load module styles if defined (Module subclasses only)
384
+ if (typeof module.loadStyles === 'function') {
385
+ await module.loadStyles()
386
+ }
387
+ await module.connect(ctx)
388
+ this._loaded.set(name, module)
389
+ this._loadOrder.push(name)
390
+ } catch (err) {
391
+ throw new ModuleLoadError(name, err as Error)
392
+ }
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Unload all modules in reverse order
398
+ */
399
+ async unloadAll(): Promise<void> {
400
+ // Unload in reverse order
401
+ const reversed = [...this._loadOrder].reverse()
402
+
403
+ for (const name of reversed) {
404
+ const module = this._loaded.get(name)
405
+ if (module && typeof module.disconnect === 'function') {
406
+ await module.disconnect()
407
+ }
408
+ }
409
+
410
+ this._loaded.clear()
411
+ this._loadOrder = []
412
+ }
413
+
414
+ /**
415
+ * Get loaded modules (for debug/introspection)
416
+ *
417
+ * @returns Map of module name to module instance
418
+ */
419
+ getModules(): Map<string, ModuleLike> {
420
+ return new Map(this._loaded)
421
+ }
422
+
423
+ /**
424
+ * Normalize any module format to Module-like interface
425
+ */
426
+ private _normalize(moduleDef: ModuleDefinition): ModuleLike {
427
+ // 1. Already a Module instance
428
+ if (moduleDef instanceof Module) {
429
+ return moduleDef as unknown as ModuleLike
430
+ }
431
+
432
+ // 2. Module class (constructor that extends Module or has static name + connect method)
433
+ if (typeof moduleDef === 'function') {
434
+ // Check if it's a class extending Module
435
+ const funcDef = moduleDef as ModuleClassConstructor
436
+ if (funcDef.prototype instanceof Module) {
437
+ return new funcDef()
438
+ }
439
+
440
+ // Check if it looks like a Module class:
441
+ // - Has own static 'name' property (not just inherited function name)
442
+ // - Prototype has connect method
443
+ const hasOwnStaticName = Object.prototype.hasOwnProperty.call(funcDef, 'name')
444
+ if (
445
+ hasOwnStaticName &&
446
+ typeof funcDef.name === 'string' &&
447
+ funcDef.name !== '' &&
448
+ typeof funcDef.prototype?.connect === 'function'
449
+ ) {
450
+ // Use adapter to properly expose static properties
451
+ return new ClassModuleAdapter(funcDef)
452
+ }
453
+
454
+ // Otherwise it's a legacy init function
455
+ return new LegacyFunctionAdapter(moduleDef as LegacyInitFunction)
456
+ }
457
+
458
+ // 3. Plain object with connect function
459
+ if (
460
+ moduleDef &&
461
+ typeof moduleDef === 'object' &&
462
+ 'connect' in moduleDef &&
463
+ typeof moduleDef.connect === 'function'
464
+ ) {
465
+ return new ObjectModuleAdapter(moduleDef as ObjectModuleDefinition)
466
+ }
467
+
468
+ throw new Error(
469
+ 'Invalid module format. Expected: Module instance, Module class, object with connect(), or function'
470
+ )
471
+ }
472
+
473
+ /**
474
+ * Get the requires array from a module (handles static vs instance properties)
475
+ */
476
+ private _getRequires(module: ModuleLike): string[] {
477
+ // Check instance property first
478
+ if (Array.isArray(module.requires)) {
479
+ return module.requires
480
+ }
481
+ // Check constructor (static) property for Module subclasses
482
+ const constructor = (module as { constructor?: { requires?: string[] } }).constructor
483
+ if (constructor && Array.isArray(constructor.requires)) {
484
+ return constructor.requires
485
+ }
486
+ return []
487
+ }
488
+
489
+ /**
490
+ * Get the priority from a module (handles static vs instance properties)
491
+ */
492
+ private _getPriority(module: ModuleLike): number {
493
+ // Check instance property first
494
+ if (typeof module.priority === 'number') {
495
+ return module.priority
496
+ }
497
+ // Check constructor (static) property for Module subclasses
498
+ const constructor = (module as { constructor?: { priority?: number } }).constructor
499
+ if (constructor && typeof constructor.priority === 'number') {
500
+ return constructor.priority
501
+ }
502
+ return 0
503
+ }
504
+
505
+ /**
506
+ * Sort modules topologically based on requires + priority
507
+ *
508
+ * Uses Kahn's algorithm for topological sort with priority tie-breaking.
509
+ *
510
+ * @returns Sorted module names
511
+ * @throws ModuleNotFoundError When a required module is not registered
512
+ * @throws CircularDependencyError When circular dependencies exist
513
+ */
514
+ private _topologicalSort(): string[] {
515
+ const modules = this._registered
516
+ const names = Array.from(modules.keys())
517
+
518
+ // Build dependency graph
519
+ // inDegree: number of dependencies for each module
520
+ // dependents: modules that depend on this module
521
+ const inDegree = new Map<string, number>()
522
+ const dependents = new Map<string, string[]>()
523
+
524
+ for (const name of names) {
525
+ inDegree.set(name, 0)
526
+ dependents.set(name, [])
527
+ }
528
+
529
+ // Process requires for each module
530
+ for (const [name, module] of modules) {
531
+ const requires = this._getRequires(module)
532
+
533
+ for (const req of requires) {
534
+ if (!modules.has(req)) {
535
+ throw new ModuleNotFoundError(req, name)
536
+ }
537
+ inDegree.set(name, (inDegree.get(name) || 0) + 1)
538
+ dependents.get(req)?.push(name)
539
+ }
540
+ }
541
+
542
+ // Initialize queue with modules that have no dependencies
543
+ // Sort by priority (lower first) for consistent ordering
544
+ const queue = names
545
+ .filter((name) => inDegree.get(name) === 0)
546
+ .sort((a, b) => {
547
+ const modA = modules.get(a)
548
+ const modB = modules.get(b)
549
+ return this._getPriority(modA!) - this._getPriority(modB!)
550
+ })
551
+
552
+ const result: string[] = []
553
+
554
+ while (queue.length > 0) {
555
+ // Take first (lowest priority)
556
+ const current = queue.shift()!
557
+ result.push(current)
558
+
559
+ // Reduce in-degree for dependents
560
+ for (const dep of dependents.get(current) || []) {
561
+ const newDegree = (inDegree.get(dep) || 0) - 1
562
+ inDegree.set(dep, newDegree)
563
+
564
+ if (newDegree === 0) {
565
+ queue.push(dep)
566
+ }
567
+ }
568
+
569
+ // Re-sort queue by priority
570
+ queue.sort((a, b) => {
571
+ const modA = modules.get(a)
572
+ const modB = modules.get(b)
573
+ return this._getPriority(modA!) - this._getPriority(modB!)
574
+ })
575
+ }
576
+
577
+ // Check for cycles
578
+ if (result.length !== names.length) {
579
+ // Find the cycle for error message
580
+ const cycle = this._findCycle(modules)
581
+ throw new CircularDependencyError(cycle)
582
+ }
583
+
584
+ return result
585
+ }
586
+
587
+ /**
588
+ * Find a cycle in the dependency graph for error reporting
589
+ */
590
+ private _findCycle(modules: Map<string, ModuleLike>): string[] {
591
+ const visited = new Set<string>()
592
+ const stack = new Set<string>()
593
+ const path: string[] = []
594
+
595
+ const dfs = (name: string): string[] | null => {
596
+ if (stack.has(name)) {
597
+ // Found cycle - extract it from path
598
+ const cycleStart = path.indexOf(name)
599
+ return [...path.slice(cycleStart), name]
600
+ }
601
+
602
+ if (visited.has(name)) {
603
+ return null
604
+ }
605
+
606
+ visited.add(name)
607
+ stack.add(name)
608
+ path.push(name)
609
+
610
+ const module = modules.get(name)
611
+ const requires = module ? this._getRequires(module) : []
612
+
613
+ for (const req of requires) {
614
+ if (modules.has(req)) {
615
+ const cycle = dfs(req)
616
+ if (cycle) {
617
+ return cycle
618
+ }
619
+ }
620
+ }
621
+
622
+ stack.delete(name)
623
+ path.pop()
624
+ return null
625
+ }
626
+
627
+ for (const name of modules.keys()) {
628
+ const cycle = dfs(name)
629
+ if (cycle) {
630
+ return cycle
631
+ }
632
+ }
633
+
634
+ return ['unknown cycle']
635
+ }
636
+ }
637
+
638
+ /**
639
+ * Factory function to create a ModuleLoader instance
640
+ */
641
+ export function createModuleLoader(): ModuleLoader {
642
+ return new ModuleLoader()
643
+ }
644
+
645
+ export default ModuleLoader
@@ -0,0 +1,11 @@
1
+ /**
2
+ * SSEBridge — qdadm re-export.
3
+ *
4
+ * The actual implementation now lives in `@quazardous/qdcore` so it can be
5
+ * shared with qdcms. The auth-aware connect/disconnect behaviour is now
6
+ * configurable via `connectOnSignal` / `disconnectOnSignal` options; defaults
7
+ * remain `'auth:login'` / `'auth:logout'` for backwards compatibility.
8
+ */
9
+
10
+ export { SSEBridge, createSSEBridge, SSE_SIGNALS } from '@quazardous/qdcore'
11
+ export type { SSEBridgeOptions } from '@quazardous/qdcore'
@@ -0,0 +1,48 @@
1
+ /**
2
+ * SignalBus - qdadm wiring.
3
+ *
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`).
7
+ *
8
+ * Signal naming conventions (qdadm flavor):
9
+ * - Generic CRUD: entity:created, entity:updated, entity:deleted
10
+ * - Entity-specific: {entityName}:created, etc. — built via `buildSignal()`
11
+ * - Auth: auth:login, auth:logout, auth:expired
12
+ * - API: api:error
13
+ *
14
+ * Wildcard subscriptions are inherited from QuarKernel:
15
+ * - 'entity:*' matches entity:created/updated/deleted
16
+ * - '*:created' matches any creation
17
+ */
18
+
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'
30
+
31
+ /**
32
+ * qdadm signal-name registry.
33
+ * Application-level convention; kept in qdadm because qdcore is naming-agnostic.
34
+ */
35
+ export const SIGNALS = {
36
+ // Generic entity lifecycle signals
37
+ ENTITY_CREATED: 'entity:created',
38
+ ENTITY_UPDATED: 'entity:updated',
39
+ ENTITY_DELETED: 'entity:deleted',
40
+
41
+ // Auth lifecycle signals
42
+ AUTH_LOGIN: 'auth:login',
43
+ AUTH_LOGOUT: 'auth:logout',
44
+ AUTH_EXPIRED: 'auth:expired', // Emitted on 401/403 API responses
45
+
46
+ // API error signals
47
+ API_ERROR: 'api:error', // Emitted on any API error { status, message, url }
48
+ } as const