@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,420 @@
1
+ /**
2
+ * EntityManager Decorator Pattern Utilities
3
+ *
4
+ * Decorators wrap an EntityManager to add cross-cutting concerns
5
+ * (auditing, caching, soft-delete) without modifying the base class.
6
+ *
7
+ * Key concepts:
8
+ * - Decorators receive a manager and return an enhanced manager
9
+ * - Decorators compose: first wraps closest to base, last wraps outermost
10
+ * - Each decorator intercepts CRUD operations, adds behavior, delegates to wrapped manager
11
+ *
12
+ * @example
13
+ * // Single decorator
14
+ * const auditedBooks = createDecoratedManager(booksManager, [
15
+ * withAuditLog(console.log)
16
+ * ])
17
+ *
18
+ * @example
19
+ * // Stacked decorators (order matters: audit wraps cache wraps base)
20
+ * const enhancedBooks = createDecoratedManager(booksManager, [
21
+ * withCache({ ttl: 60000 }),
22
+ * withAuditLog(logger.info)
23
+ * ])
24
+ */
25
+
26
+ /**
27
+ * Base entity manager interface for decorators
28
+ */
29
+ export interface DecoratorEntityManager {
30
+ name: string
31
+ idField: string
32
+ create: (data: Record<string, unknown>) => Promise<Record<string, unknown>>
33
+ update: (id: unknown, data: Record<string, unknown>) => Promise<Record<string, unknown>>
34
+ patch: (id: unknown, data: Record<string, unknown>) => Promise<Record<string, unknown>>
35
+ delete: (id: unknown) => Promise<void>
36
+ [key: string]: unknown
37
+ }
38
+
39
+ /**
40
+ * Decorator function type
41
+ */
42
+ export type ManagerDecorator = (manager: DecoratorEntityManager) => DecoratorEntityManager
43
+
44
+ /**
45
+ * Apply a chain of decorators to an EntityManager
46
+ *
47
+ * Decorators are applied in order: first decorator wraps the base manager,
48
+ * second wraps the first, etc. This means the last decorator in the array
49
+ * is the outermost layer (executed first on method calls).
50
+ *
51
+ * @param manager - Base EntityManager to decorate
52
+ * @param decorators - Array of decorator functions
53
+ * @returns Decorated manager
54
+ *
55
+ * @example
56
+ * // Decorator function signature
57
+ * const myDecorator = (options) => (manager) => {
58
+ * return {
59
+ * ...manager,
60
+ * async create(data) {
61
+ * // Pre-processing
62
+ * const result = await manager.create(data)
63
+ * // Post-processing
64
+ * return result
65
+ * }
66
+ * }
67
+ * }
68
+ */
69
+ export function createDecoratedManager(
70
+ manager: DecoratorEntityManager,
71
+ decorators: ManagerDecorator[] = []
72
+ ): DecoratorEntityManager {
73
+ if (!manager) {
74
+ throw new Error('[createDecoratedManager] Manager is required')
75
+ }
76
+ if (!Array.isArray(decorators)) {
77
+ throw new Error('[createDecoratedManager] Decorators must be an array')
78
+ }
79
+
80
+ // Apply decorators in sequence (first wraps base, last is outermost)
81
+ return decorators.reduce((decorated, decorator) => {
82
+ if (typeof decorator !== 'function') {
83
+ throw new Error('[createDecoratedManager] Each decorator must be a function')
84
+ }
85
+ return decorator(decorated)
86
+ }, manager)
87
+ }
88
+
89
+ /**
90
+ * Audit log action details
91
+ */
92
+ export interface AuditLogDetails {
93
+ entity: string
94
+ timestamp: string
95
+ id?: unknown
96
+ data?: Record<string, unknown>
97
+ }
98
+
99
+ /**
100
+ * Audit log function type
101
+ */
102
+ export type AuditLogger = (action: string, details: AuditLogDetails) => void
103
+
104
+ /**
105
+ * Audit log decorator options
106
+ */
107
+ export interface AuditLogDecoratorOptions {
108
+ includeData?: boolean
109
+ }
110
+
111
+ /**
112
+ * Audit log decorator factory
113
+ *
114
+ * Logs CRUD operations with timestamps. Useful for tracking changes,
115
+ * debugging, or audit trails.
116
+ *
117
+ * @param logger - Logging function (receives action, details)
118
+ * @param options - Options
119
+ * @returns Decorator function
120
+ *
121
+ * @example
122
+ * const auditedManager = createDecoratedManager(manager, [
123
+ * withAuditLog(console.log)
124
+ * ])
125
+ *
126
+ * @example
127
+ * // With custom logger
128
+ * const auditedManager = createDecoratedManager(manager, [
129
+ * withAuditLog((action, details) => {
130
+ * auditService.log({ action, ...details, timestamp: new Date() })
131
+ * }, { includeData: true })
132
+ * ])
133
+ */
134
+ export function withAuditLog(
135
+ logger: AuditLogger,
136
+ options: AuditLogDecoratorOptions = {}
137
+ ): ManagerDecorator {
138
+ if (typeof logger !== 'function') {
139
+ throw new Error('[withAuditLog] Logger must be a function')
140
+ }
141
+
142
+ const { includeData = false } = options
143
+
144
+ return (manager: DecoratorEntityManager): DecoratorEntityManager => {
145
+ const entityName = manager.name
146
+
147
+ const logAction = (action: string, details: Partial<AuditLogDetails>) => {
148
+ logger(action, {
149
+ entity: entityName,
150
+ timestamp: new Date().toISOString(),
151
+ ...details,
152
+ } as AuditLogDetails)
153
+ }
154
+
155
+ return {
156
+ // Preserve all manager properties and methods
157
+ ...manager,
158
+ // Ensure name is accessible (might be a getter)
159
+ get name() {
160
+ return manager.name
161
+ },
162
+
163
+ async create(data: Record<string, unknown>) {
164
+ const result = await manager.create(data)
165
+ const logDetails: Partial<AuditLogDetails> = { id: result?.[manager.idField] }
166
+ if (includeData) logDetails.data = data
167
+ logAction('create', logDetails)
168
+ return result
169
+ },
170
+
171
+ async update(id: unknown, data: Record<string, unknown>) {
172
+ const result = await manager.update(id, data)
173
+ const logDetails: Partial<AuditLogDetails> = { id }
174
+ if (includeData) logDetails.data = data
175
+ logAction('update', logDetails)
176
+ return result
177
+ },
178
+
179
+ async patch(id: unknown, data: Record<string, unknown>) {
180
+ const result = await manager.patch(id, data)
181
+ const logDetails: Partial<AuditLogDetails> = { id }
182
+ if (includeData) logDetails.data = data
183
+ logAction('patch', logDetails)
184
+ return result
185
+ },
186
+
187
+ async delete(id: unknown) {
188
+ await manager.delete(id)
189
+ logAction('delete', { id })
190
+ },
191
+ }
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Soft delete decorator options
197
+ */
198
+ export interface SoftDeleteDecoratorOptions {
199
+ field?: string
200
+ timestamp?: () => string | number
201
+ }
202
+
203
+ /**
204
+ * Soft delete decorator factory
205
+ *
206
+ * Converts delete() to update with deleted_at timestamp.
207
+ * Useful for audit trails, undo functionality, or legal compliance.
208
+ *
209
+ * @param options - Options
210
+ * @returns Decorator function
211
+ *
212
+ * @example
213
+ * const softDeleteManager = createDecoratedManager(manager, [
214
+ * withSoftDelete()
215
+ * ])
216
+ *
217
+ * // Instead of deleting, sets deleted_at
218
+ * await softDeleteManager.delete(1)
219
+ * // Record: { id: 1, ..., deleted_at: '2024-01-01T00:00:00.000Z' }
220
+ *
221
+ * @example
222
+ * // Custom field and timestamp
223
+ * const softDeleteManager = createDecoratedManager(manager, [
224
+ * withSoftDelete({
225
+ * field: 'removed_at',
226
+ * timestamp: () => Date.now()
227
+ * })
228
+ * ])
229
+ */
230
+ export function withSoftDelete(options: SoftDeleteDecoratorOptions = {}): ManagerDecorator {
231
+ const { field = 'deleted_at', timestamp = () => new Date().toISOString() } = options
232
+
233
+ return (manager: DecoratorEntityManager): DecoratorEntityManager => ({
234
+ ...manager,
235
+ get name() {
236
+ return manager.name
237
+ },
238
+
239
+ async delete(id: unknown) {
240
+ // Convert delete to update with soft-delete field
241
+ await manager.patch(id, { [field]: timestamp() })
242
+ },
243
+ })
244
+ }
245
+
246
+ /**
247
+ * Timestamp decorator options
248
+ */
249
+ export interface TimestampDecoratorOptions {
250
+ createdAtField?: string
251
+ updatedAtField?: string
252
+ timestamp?: () => string
253
+ }
254
+
255
+ /**
256
+ * Timestamp decorator factory
257
+ *
258
+ * Automatically adds created_at and updated_at timestamps to records.
259
+ *
260
+ * @param options - Options
261
+ * @returns Decorator function
262
+ *
263
+ * @example
264
+ * const timestampedManager = createDecoratedManager(manager, [
265
+ * withTimestamps()
266
+ * ])
267
+ *
268
+ * await timestampedManager.create({ title: 'Test' })
269
+ * // Record: { title: 'Test', created_at: '...', updated_at: '...' }
270
+ *
271
+ * await timestampedManager.update(1, { title: 'Updated' })
272
+ * // Record: { title: 'Updated', updated_at: '...' }
273
+ */
274
+ export function withTimestamps(options: TimestampDecoratorOptions = {}): ManagerDecorator {
275
+ const {
276
+ createdAtField = 'created_at',
277
+ updatedAtField = 'updated_at',
278
+ timestamp = () => new Date().toISOString(),
279
+ } = options
280
+
281
+ return (manager: DecoratorEntityManager): DecoratorEntityManager => ({
282
+ ...manager,
283
+ get name() {
284
+ return manager.name
285
+ },
286
+
287
+ async create(data: Record<string, unknown>) {
288
+ const now = timestamp()
289
+ return manager.create({
290
+ ...data,
291
+ [createdAtField]: now,
292
+ [updatedAtField]: now,
293
+ })
294
+ },
295
+
296
+ async update(id: unknown, data: Record<string, unknown>) {
297
+ return manager.update(id, {
298
+ ...data,
299
+ [updatedAtField]: timestamp(),
300
+ })
301
+ },
302
+
303
+ async patch(id: unknown, data: Record<string, unknown>) {
304
+ return manager.patch(id, {
305
+ ...data,
306
+ [updatedAtField]: timestamp(),
307
+ })
308
+ },
309
+ })
310
+ }
311
+
312
+ /**
313
+ * Validation context
314
+ */
315
+ export interface ValidationContext {
316
+ action: 'create' | 'update' | 'patch'
317
+ id?: unknown
318
+ manager: DecoratorEntityManager
319
+ }
320
+
321
+ /**
322
+ * Validation errors
323
+ */
324
+ export type ValidationErrors = Record<string, string>
325
+
326
+ /**
327
+ * Validator function type
328
+ */
329
+ export type ValidatorFn = (
330
+ data: Record<string, unknown>,
331
+ context: ValidationContext
332
+ ) => ValidationErrors | null
333
+
334
+ /**
335
+ * Validation error
336
+ */
337
+ export interface ValidationError extends Error {
338
+ name: 'ValidationError'
339
+ errors: ValidationErrors
340
+ }
341
+
342
+ /**
343
+ * Validation decorator options
344
+ */
345
+ export interface ValidationDecoratorOptions {
346
+ onUpdate?: boolean
347
+ onPatch?: boolean
348
+ }
349
+
350
+ /**
351
+ * Validation decorator factory
352
+ *
353
+ * Validates data before create/update operations.
354
+ * Throws ValidationError if validation fails.
355
+ *
356
+ * @param validator - Validation function (data, context) => errors | null
357
+ * @param options - Options
358
+ * @returns Decorator function
359
+ *
360
+ * @example
361
+ * const validatedManager = createDecoratedManager(manager, [
362
+ * withValidation((data) => {
363
+ * const errors = {}
364
+ * if (!data.title) errors.title = 'Title is required'
365
+ * if (data.price < 0) errors.price = 'Price must be positive'
366
+ * return Object.keys(errors).length ? errors : null
367
+ * })
368
+ * ])
369
+ *
370
+ * await validatedManager.create({ price: -5 })
371
+ * // Throws: ValidationError { errors: { title: '...', price: '...' } }
372
+ */
373
+ export function withValidation(
374
+ validator: ValidatorFn,
375
+ options: ValidationDecoratorOptions = {}
376
+ ): ManagerDecorator {
377
+ if (typeof validator !== 'function') {
378
+ throw new Error('[withValidation] Validator must be a function')
379
+ }
380
+
381
+ const { onUpdate = true, onPatch = true } = options
382
+
383
+ return (manager: DecoratorEntityManager): DecoratorEntityManager => {
384
+ const validate = (data: Record<string, unknown>, context: ValidationContext) => {
385
+ const errors = validator(data, context)
386
+ if (errors && Object.keys(errors).length > 0) {
387
+ const error = new Error('Validation failed') as ValidationError
388
+ error.name = 'ValidationError'
389
+ error.errors = errors
390
+ throw error
391
+ }
392
+ }
393
+
394
+ return {
395
+ ...manager,
396
+ get name() {
397
+ return manager.name
398
+ },
399
+
400
+ async create(data: Record<string, unknown>) {
401
+ validate(data, { action: 'create', manager })
402
+ return manager.create(data)
403
+ },
404
+
405
+ async update(id: unknown, data: Record<string, unknown>) {
406
+ if (onUpdate) {
407
+ validate(data, { action: 'update', id, manager })
408
+ }
409
+ return manager.update(id, data)
410
+ },
411
+
412
+ async patch(id: unknown, data: Record<string, unknown>) {
413
+ if (onPatch) {
414
+ validate(data, { action: 'patch', id, manager })
415
+ }
416
+ return manager.patch(id, data)
417
+ },
418
+ }
419
+ }
420
+ }