@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,470 @@
1
+ /**
2
+ * extendModule - Module extension helper for qdadm
3
+ *
4
+ * Provides a clean API for one module to extend another module's
5
+ * configuration and behavior through the hook system.
6
+ *
7
+ * Extension types:
8
+ * - columns: adds columns via `{target}:list:alter` hook
9
+ * - fields: adds fields via `{target}:form:alter` hook
10
+ * - filters: adds filters via `{target}:filter:alter` hook
11
+ * - blocks: injects blocks via zone registry
12
+ *
13
+ * @example
14
+ * // Simple config object approach
15
+ * const cleanup = extendModule('books', {
16
+ * columns: [
17
+ * { field: 'rating', header: 'Rating', width: 100 }
18
+ * ],
19
+ * fields: [
20
+ * { name: 'rating', type: 'number', label: 'Rating' }
21
+ * ],
22
+ * blocks: {
23
+ * 'books:detail:sidebar': [
24
+ * { component: RatingWidget, weight: 20 }
25
+ * ]
26
+ * }
27
+ * }, { hooks, zones })
28
+ *
29
+ * // Later: cleanup() // Removes all registered extensions
30
+ *
31
+ * @example
32
+ * // Fluent API approach
33
+ * const cleanup = extendModule('books')
34
+ * .addColumn({ field: 'rating', header: 'Rating' })
35
+ * .addField({ name: 'rating', type: 'number' })
36
+ * .addBlock('books:detail:sidebar', { component: RatingWidget })
37
+ * .register({ hooks, zones })
38
+ */
39
+
40
+ import type { Component } from 'vue'
41
+ import type { HookRegistry } from '../hooks/HookRegistry'
42
+ import type { ZoneRegistry } from '../zones/ZoneRegistry'
43
+
44
+ /**
45
+ * Default priority for extension hooks
46
+ */
47
+ const DEFAULT_PRIORITY = 50
48
+
49
+ /**
50
+ * Column configuration
51
+ */
52
+ export interface ExtensionColumn {
53
+ field: string
54
+ header?: string
55
+ width?: number
56
+ body?: (data: unknown) => unknown
57
+ [key: string]: unknown
58
+ }
59
+
60
+ /**
61
+ * Field configuration
62
+ */
63
+ export interface ExtensionField {
64
+ name: string
65
+ type?: string
66
+ label?: string
67
+ [key: string]: unknown
68
+ }
69
+
70
+ /**
71
+ * Filter configuration
72
+ */
73
+ export interface ExtensionFilter {
74
+ name: string
75
+ type?: string
76
+ label?: string
77
+ [key: string]: unknown
78
+ }
79
+
80
+ /**
81
+ * Block configuration
82
+ */
83
+ export interface ExtensionBlock {
84
+ component: Component
85
+ weight?: number
86
+ props?: Record<string, unknown>
87
+ id?: string
88
+ [key: string]: unknown
89
+ }
90
+
91
+ /**
92
+ * Extensions config object
93
+ */
94
+ export interface ExtensionsConfig {
95
+ columns?: ExtensionColumn[]
96
+ fields?: ExtensionField[]
97
+ filters?: ExtensionFilter[]
98
+ blocks?: Record<string, ExtensionBlock[]>
99
+ }
100
+
101
+ /**
102
+ * Registration context
103
+ */
104
+ export interface ExtensionContext {
105
+ hooks: HookRegistry
106
+ zones?: ZoneRegistry
107
+ priority?: number
108
+ }
109
+
110
+ /**
111
+ * Collected extensions config for serialization
112
+ */
113
+ export interface CollectedExtensions {
114
+ target: string
115
+ columns?: ExtensionColumn[]
116
+ fields?: ExtensionField[]
117
+ filters?: ExtensionFilter[]
118
+ blocks?: Record<string, ExtensionBlock[]>
119
+ }
120
+
121
+ /**
122
+ * Block ID reference for cleanup
123
+ */
124
+ interface BlockIdRef {
125
+ zoneName: string
126
+ id: string
127
+ }
128
+
129
+ /**
130
+ * Extension builder for fluent API
131
+ */
132
+ export class ExtensionBuilder {
133
+ private _target: string
134
+ private _columns: ExtensionColumn[]
135
+ private _fields: ExtensionField[]
136
+ private _filters: ExtensionFilter[]
137
+ private _blocks: Map<string, ExtensionBlock[]>
138
+
139
+ /**
140
+ * @param target - Target module name to extend
141
+ */
142
+ constructor(target: string) {
143
+ if (!target || typeof target !== 'string') {
144
+ throw new Error('[extendModule] Target module name must be a non-empty string')
145
+ }
146
+ this._target = target
147
+ this._columns = []
148
+ this._fields = []
149
+ this._filters = []
150
+ this._blocks = new Map()
151
+ }
152
+
153
+ /**
154
+ * Add a column to the target module's list view
155
+ *
156
+ * @param column - Column configuration
157
+ * @returns This instance for chaining
158
+ */
159
+ addColumn(column: ExtensionColumn): this {
160
+ if (!column || !column.field) {
161
+ throw new Error('[extendModule] Column must have a field property')
162
+ }
163
+ this._columns.push(column)
164
+ return this
165
+ }
166
+
167
+ /**
168
+ * Add multiple columns to the target module's list view
169
+ *
170
+ * @param columns - Array of column configurations
171
+ * @returns This instance for chaining
172
+ */
173
+ addColumns(columns: ExtensionColumn[]): this {
174
+ if (!Array.isArray(columns)) {
175
+ throw new Error('[extendModule] addColumns expects an array')
176
+ }
177
+ for (const column of columns) {
178
+ this.addColumn(column)
179
+ }
180
+ return this
181
+ }
182
+
183
+ /**
184
+ * Add a field to the target module's form view
185
+ *
186
+ * @param field - Field configuration
187
+ * @returns This instance for chaining
188
+ */
189
+ addField(field: ExtensionField): this {
190
+ if (!field || !field.name) {
191
+ throw new Error('[extendModule] Field must have a name property')
192
+ }
193
+ this._fields.push(field)
194
+ return this
195
+ }
196
+
197
+ /**
198
+ * Add multiple fields to the target module's form view
199
+ *
200
+ * @param fields - Array of field configurations
201
+ * @returns This instance for chaining
202
+ */
203
+ addFields(fields: ExtensionField[]): this {
204
+ if (!Array.isArray(fields)) {
205
+ throw new Error('[extendModule] addFields expects an array')
206
+ }
207
+ for (const field of fields) {
208
+ this.addField(field)
209
+ }
210
+ return this
211
+ }
212
+
213
+ /**
214
+ * Add a filter to the target module's filter bar
215
+ *
216
+ * @param filter - Filter configuration
217
+ * @returns This instance for chaining
218
+ */
219
+ addFilter(filter: ExtensionFilter): this {
220
+ if (!filter || !filter.name) {
221
+ throw new Error('[extendModule] Filter must have a name property')
222
+ }
223
+ this._filters.push(filter)
224
+ return this
225
+ }
226
+
227
+ /**
228
+ * Add multiple filters to the target module's filter bar
229
+ *
230
+ * @param filters - Array of filter configurations
231
+ * @returns This instance for chaining
232
+ */
233
+ addFilters(filters: ExtensionFilter[]): this {
234
+ if (!Array.isArray(filters)) {
235
+ throw new Error('[extendModule] addFilters expects an array')
236
+ }
237
+ for (const filter of filters) {
238
+ this.addFilter(filter)
239
+ }
240
+ return this
241
+ }
242
+
243
+ /**
244
+ * Add a block to a zone in the target module
245
+ *
246
+ * @param zoneName - Zone name (e.g., 'books:detail:sidebar')
247
+ * @param block - Block configuration
248
+ * @returns This instance for chaining
249
+ */
250
+ addBlock(zoneName: string, block: ExtensionBlock): this {
251
+ if (!zoneName || typeof zoneName !== 'string') {
252
+ throw new Error('[extendModule] Zone name must be a non-empty string')
253
+ }
254
+ if (!block || !block.component) {
255
+ throw new Error('[extendModule] Block must have a component property')
256
+ }
257
+ if (!this._blocks.has(zoneName)) {
258
+ this._blocks.set(zoneName, [])
259
+ }
260
+ this._blocks.get(zoneName)!.push(block)
261
+ return this
262
+ }
263
+
264
+ /**
265
+ * Add multiple blocks to a zone
266
+ *
267
+ * @param zoneName - Zone name
268
+ * @param blocks - Array of block configurations
269
+ * @returns This instance for chaining
270
+ */
271
+ addBlocks(zoneName: string, blocks: ExtensionBlock[]): this {
272
+ if (!Array.isArray(blocks)) {
273
+ throw new Error('[extendModule] addBlocks expects an array')
274
+ }
275
+ for (const block of blocks) {
276
+ this.addBlock(zoneName, block)
277
+ }
278
+ return this
279
+ }
280
+
281
+ /**
282
+ * Register all extensions with the kernel
283
+ *
284
+ * @param context - Registration context
285
+ * @returns Cleanup function to remove all extensions
286
+ */
287
+ register(context: ExtensionContext): () => void {
288
+ if (!context || !context.hooks) {
289
+ throw new Error('[extendModule] register() requires { hooks } context')
290
+ }
291
+
292
+ const { hooks, zones, priority = DEFAULT_PRIORITY } = context
293
+ const cleanupFns: Array<() => void> = []
294
+
295
+ // Register column alter hook
296
+ if (this._columns.length > 0) {
297
+ const hookName = `${this._target}:list:alter`
298
+ const columns = [...this._columns]
299
+ const unbind = hooks.register(
300
+ hookName,
301
+ (config) => {
302
+ const cfg = config as { columns?: ExtensionColumn[] }
303
+ if (!cfg.columns) {
304
+ cfg.columns = []
305
+ }
306
+ cfg.columns.push(...columns)
307
+ return config
308
+ },
309
+ { priority }
310
+ )
311
+ cleanupFns.push(unbind)
312
+ }
313
+
314
+ // Register field alter hook
315
+ if (this._fields.length > 0) {
316
+ const hookName = `${this._target}:form:alter`
317
+ const fields = [...this._fields]
318
+ const unbind = hooks.register(
319
+ hookName,
320
+ (config) => {
321
+ const cfg = config as { fields?: ExtensionField[] }
322
+ if (!cfg.fields) {
323
+ cfg.fields = []
324
+ }
325
+ cfg.fields.push(...fields)
326
+ return config
327
+ },
328
+ { priority }
329
+ )
330
+ cleanupFns.push(unbind)
331
+ }
332
+
333
+ // Register filter alter hook
334
+ if (this._filters.length > 0) {
335
+ const hookName = `${this._target}:filter:alter`
336
+ const filters = [...this._filters]
337
+ const unbind = hooks.register(
338
+ hookName,
339
+ (config) => {
340
+ const cfg = config as { filters?: ExtensionFilter[] }
341
+ if (!cfg.filters) {
342
+ cfg.filters = []
343
+ }
344
+ cfg.filters.push(...filters)
345
+ return config
346
+ },
347
+ { priority }
348
+ )
349
+ cleanupFns.push(unbind)
350
+ }
351
+
352
+ // Register zone blocks
353
+ if (this._blocks.size > 0) {
354
+ if (!zones) {
355
+ throw new Error('[extendModule] register() requires { zones } context when blocks are defined')
356
+ }
357
+ for (const [zoneName, blocks] of this._blocks) {
358
+ for (const block of blocks) {
359
+ zones.registerBlock(zoneName, block)
360
+ }
361
+ }
362
+ // Zone blocks cleanup: store zone/id pairs for removal
363
+ const blockIds: BlockIdRef[] = []
364
+ for (const [zoneName, blocks] of this._blocks) {
365
+ for (const block of blocks) {
366
+ if (block.id) {
367
+ blockIds.push({ zoneName, id: block.id })
368
+ }
369
+ }
370
+ }
371
+ if (blockIds.length > 0) {
372
+ cleanupFns.push(() => {
373
+ for (const { zoneName, id } of blockIds) {
374
+ zones.removeBlock(zoneName, id)
375
+ }
376
+ })
377
+ }
378
+ }
379
+
380
+ // Return cleanup function
381
+ return () => {
382
+ for (const cleanup of cleanupFns) {
383
+ cleanup()
384
+ }
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Get collected extensions as a config object
390
+ *
391
+ * Useful for debugging or serialization.
392
+ *
393
+ * @returns Extensions config
394
+ */
395
+ toConfig(): CollectedExtensions {
396
+ const config: CollectedExtensions = {
397
+ target: this._target,
398
+ }
399
+ if (this._columns.length > 0) {
400
+ config.columns = [...this._columns]
401
+ }
402
+ if (this._fields.length > 0) {
403
+ config.fields = [...this._fields]
404
+ }
405
+ if (this._filters.length > 0) {
406
+ config.filters = [...this._filters]
407
+ }
408
+ if (this._blocks.size > 0) {
409
+ config.blocks = {}
410
+ for (const [zoneName, blocks] of this._blocks) {
411
+ config.blocks[zoneName] = [...blocks]
412
+ }
413
+ }
414
+ return config
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Create an extension for a target module
420
+ *
421
+ * Can be used in two ways:
422
+ * 1. Fluent API: extendModule('target').addColumn(...).register({ hooks })
423
+ * 2. Config object: extendModule('target', { columns: [...] }, { hooks })
424
+ *
425
+ * @param target - Target module name to extend
426
+ * @param extensions - Optional extensions config object
427
+ * @param context - Registration context (required if extensions provided)
428
+ * @returns Builder for fluent API, or cleanup function if extensions provided
429
+ */
430
+ export function extendModule(target: string): ExtensionBuilder
431
+ export function extendModule(
432
+ target: string,
433
+ extensions: ExtensionsConfig,
434
+ context: ExtensionContext
435
+ ): () => void
436
+ export function extendModule(
437
+ target: string,
438
+ extensions?: ExtensionsConfig,
439
+ context?: ExtensionContext
440
+ ): ExtensionBuilder | (() => void) {
441
+ const builder = new ExtensionBuilder(target)
442
+
443
+ // If no extensions provided, return builder for fluent API
444
+ if (!extensions) {
445
+ return builder
446
+ }
447
+
448
+ // Config object approach: apply extensions and register immediately
449
+ if (extensions.columns) {
450
+ builder.addColumns(extensions.columns)
451
+ }
452
+ if (extensions.fields) {
453
+ builder.addFields(extensions.fields)
454
+ }
455
+ if (extensions.filters) {
456
+ builder.addFilters(extensions.filters)
457
+ }
458
+ if (extensions.blocks) {
459
+ for (const [zoneName, blocks] of Object.entries(extensions.blocks)) {
460
+ builder.addBlocks(zoneName, blocks)
461
+ }
462
+ }
463
+
464
+ // Context required for config object approach
465
+ if (!context) {
466
+ throw new Error('[extendModule] Context { hooks } is required when extensions are provided')
467
+ }
468
+
469
+ return builder.register(context)
470
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Core Module
3
+ *
4
+ * Extension and helper utilities for qdadm modules.
5
+ */
6
+
7
+ export {
8
+ extendModule,
9
+ ExtensionBuilder,
10
+ type ExtensionColumn,
11
+ type ExtensionField,
12
+ type ExtensionFilter,
13
+ type ExtensionBlock,
14
+ type ExtensionsConfig,
15
+ type ExtensionContext,
16
+ type CollectedExtensions,
17
+ } from './extension'
18
+
19
+ export {
20
+ createDecoratedManager,
21
+ withValidation,
22
+ withAuditLog as withAuditLogDecorator,
23
+ withSoftDelete as withSoftDeleteDecorator,
24
+ withTimestamps as withTimestampsDecorator,
25
+ type DecoratorEntityManager,
26
+ type ManagerDecorator,
27
+ type AuditLogDetails,
28
+ type AuditLogger,
29
+ type AuditLogDecoratorOptions,
30
+ type SoftDeleteDecoratorOptions,
31
+ type TimestampDecoratorOptions,
32
+ type ValidationContext,
33
+ type ValidationErrors,
34
+ type ValidatorFn,
35
+ type ValidationError,
36
+ type ValidationDecoratorOptions,
37
+ } from './decorator'
38
+
39
+ export {
40
+ createHookBundle,
41
+ applyBundle,
42
+ applyBundles,
43
+ withSoftDelete,
44
+ withTimestamps,
45
+ withVersioning,
46
+ withAuditLog,
47
+ type BundleRegisterFn,
48
+ type BundleContext,
49
+ type BundleSetupFn,
50
+ type Bundle,
51
+ type BundleFactory,
52
+ type ApplyBundleContext,
53
+ type SoftDeleteOptions,
54
+ type TimestampOptions,
55
+ type VersioningOptions,
56
+ type VersionConflictError,
57
+ type AuditLogOptions,
58
+ type AuditLogEntry,
59
+ } from './bundles'