@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.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/package.json +101 -0
- package/src/assets/logo.svg +6 -0
- package/src/auth/SessionAuthAdapter.ts +459 -0
- package/src/auth/index.ts +15 -0
- package/src/chain/ActiveStack.ts +154 -0
- package/src/chain/StackHydrator.ts +292 -0
- package/src/chain/index.ts +25 -0
- package/src/chain/useActiveStack.ts +120 -0
- package/src/chain/useStackHydrator.ts +155 -0
- package/src/components/BoolCell.vue +34 -0
- package/src/components/InfoBanner.vue +73 -0
- package/src/components/QdadmRoot.vue +52 -0
- package/src/components/SeverityTag.vue +86 -0
- package/src/components/dialogs/BulkStatusDialog.vue +60 -0
- package/src/components/dialogs/MultiStepDialog.vue +331 -0
- package/src/components/dialogs/SimpleDialog.vue +122 -0
- package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
- package/src/components/display/BannerZone.vue +65 -0
- package/src/components/display/CardsGrid.vue +72 -0
- package/src/components/display/CopyableId.vue +84 -0
- package/src/components/display/EmptyState.vue +62 -0
- package/src/components/display/IntensityBar.vue +190 -0
- package/src/components/display/RichCardsGrid.vue +101 -0
- package/src/components/edit/FormActions.vue +78 -0
- package/src/components/edit/FormField.vue +82 -0
- package/src/components/edit/FormInput.vue +158 -0
- package/src/components/edit/FormPage.vue +260 -0
- package/src/components/edit/FormTab.vue +217 -0
- package/src/components/edit/FormTabs.vue +120 -0
- package/src/components/edit/LookupField.vue +406 -0
- package/src/components/edit/LookupPickerDialog.vue +230 -0
- package/src/components/editors/JsonEditorFoldable.vue +470 -0
- package/src/components/editors/JsonStructuredField.vue +264 -0
- package/src/components/editors/JsonViewer.vue +51 -0
- package/src/components/editors/KeyValueEditor.vue +314 -0
- package/src/components/editors/LanguageEditor.vue +259 -0
- package/src/components/editors/PermissionEditor.vue +558 -0
- package/src/components/editors/ScopeEditor.vue +372 -0
- package/src/components/editors/VanillaJsonEditor.vue +183 -0
- package/src/components/index.ts +83 -0
- package/src/components/item/FieldGroups.vue +392 -0
- package/src/components/layout/AppLayout.vue +1032 -0
- package/src/components/layout/BaseLayout.vue +190 -0
- package/src/components/layout/Breadcrumb.vue +55 -0
- package/src/components/layout/DashboardLayout.vue +104 -0
- package/src/components/layout/FormLayout.vue +256 -0
- package/src/components/layout/ListLayout.vue +278 -0
- package/src/components/layout/PageHeader.vue +156 -0
- package/src/components/layout/PageLayout.vue +126 -0
- package/src/components/layout/PageNav.vue +192 -0
- package/src/components/layout/SidebarBox.vue +87 -0
- package/src/components/layout/Zone.vue +182 -0
- package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
- package/src/components/layout/defaults/DefaultFooter.vue +89 -0
- package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
- package/src/components/layout/defaults/DefaultHeader.vue +78 -0
- package/src/components/layout/defaults/DefaultMenu.vue +188 -0
- package/src/components/layout/defaults/DefaultPagination.vue +75 -0
- package/src/components/layout/defaults/DefaultTable.vue +137 -0
- package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
- package/src/components/layout/defaults/index.ts +16 -0
- package/src/components/lists/ActionButtons.vue +49 -0
- package/src/components/lists/ActionColumn.vue +40 -0
- package/src/components/lists/FilterBar.vue +54 -0
- package/src/components/lists/ListPage.vue +467 -0
- package/src/components/pages/LoginPage.vue +334 -0
- package/src/components/pages/NotFoundPage.vue +98 -0
- package/src/components/show/ShowDisplay.vue +343 -0
- package/src/components/show/ShowField.vue +67 -0
- package/src/components/show/ShowPage.vue +431 -0
- package/src/composables/index.ts +155 -0
- package/src/composables/useApp.ts +67 -0
- package/src/composables/useAuth.ts +98 -0
- package/src/composables/useBareForm.ts +319 -0
- package/src/composables/useBreadcrumb.ts +252 -0
- package/src/composables/useChildPage.ts +113 -0
- package/src/composables/useCurrentEntity.ts +60 -0
- package/src/composables/useDeferred.ts +94 -0
- package/src/composables/useDirtyState.ts +138 -0
- package/src/composables/useEntityItemFormPage.ts +942 -0
- package/src/composables/useEntityItemFormPage.types.ts +451 -0
- package/src/composables/useEntityItemPage.ts +438 -0
- package/src/composables/useEntityItemShowPage.ts +722 -0
- package/src/composables/useFieldManager.ts +692 -0
- package/src/composables/useForm.ts +544 -0
- package/src/composables/useGuardStore.ts +53 -0
- package/src/composables/useHooks.ts +56 -0
- package/src/composables/useInfoBanner.ts +217 -0
- package/src/composables/useJsonSyntax.ts +122 -0
- package/src/composables/useLayoutResolver.ts +329 -0
- package/src/composables/useListPage.ts +1535 -0
- package/src/composables/useListPage.types.ts +437 -0
- package/src/composables/useListPage.utils.ts +101 -0
- package/src/composables/useNavContext.ts +439 -0
- package/src/composables/useNavigation.ts +216 -0
- package/src/composables/useOptionsLookup.ts +323 -0
- package/src/composables/usePageTitle.ts +84 -0
- package/src/composables/useSSEBridge.ts +142 -0
- package/src/composables/useSemanticBreadcrumb.ts +222 -0
- package/src/composables/useSidebarState.ts +24 -0
- package/src/composables/useSignals.ts +52 -0
- package/src/composables/useUnsavedChangesGuard.ts +155 -0
- package/src/composables/useUserImpersonator.ts +317 -0
- package/src/composables/useZoneRegistry.ts +252 -0
- package/src/core/bundles.ts +558 -0
- package/src/core/decorator.ts +420 -0
- package/src/core/extension.ts +470 -0
- package/src/core/index.ts +59 -0
- package/src/deferred/DeferredRegistry.ts +353 -0
- package/src/deferred/index.ts +15 -0
- package/src/editors/index.ts +12 -0
- package/src/entity/EntityManager.cache.ts +530 -0
- package/src/entity/EntityManager.crud.ts +615 -0
- package/src/entity/EntityManager.interface.ts +78 -0
- package/src/entity/EntityManager.query.ts +221 -0
- package/src/entity/EntityManager.relations.ts +151 -0
- package/src/entity/EntityManager.ts +944 -0
- package/src/entity/EntityManager.types.ts +288 -0
- package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
- package/src/entity/auth/EntityAuthAdapter.ts +119 -0
- package/src/entity/auth/PermissiveAdapter.ts +43 -0
- package/src/entity/auth/RoleHierarchy.ts +128 -0
- package/src/entity/auth/SecurityChecker.ts +202 -0
- package/src/entity/auth/factory.ts +203 -0
- package/src/entity/auth/index.ts +47 -0
- package/src/entity/factory.test.js +189 -0
- package/src/entity/factory.ts +146 -0
- package/src/entity/index.ts +49 -0
- package/src/entity/storage/ApiStorage.ts +228 -0
- package/src/entity/storage/IStorage.ts +100 -0
- package/src/entity/storage/LocalStorage.ts +200 -0
- package/src/entity/storage/MemoryStorage.ts +184 -0
- package/src/entity/storage/MockApiStorage.ts +318 -0
- package/src/entity/storage/SdkStorage.ts +377 -0
- package/src/entity/storage/factory.test.js +159 -0
- package/src/entity/storage/factory.ts +209 -0
- package/src/entity/storage/index.ts +75 -0
- package/src/gen/FieldMapper.ts +122 -0
- package/src/gen/StorageProfileFactory.ts +93 -0
- package/src/gen/connectors/BaseConnector.ts +150 -0
- package/src/gen/connectors/ManualConnector.test.js +499 -0
- package/src/gen/connectors/ManualConnector.ts +414 -0
- package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
- package/src/gen/connectors/OpenAPIConnector.ts +693 -0
- package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
- package/src/gen/connectors/index.ts +27 -0
- package/src/gen/createManagers.ts +245 -0
- package/src/gen/decorators.ts +134 -0
- package/src/gen/generateManagers.test.js +540 -0
- package/src/gen/generateManagers.ts +398 -0
- package/src/gen/index.ts +66 -0
- package/src/gen/schema.ts +248 -0
- package/src/gen/vite-plugin.ts +114 -0
- package/src/generated/managers/testManager.js +45 -0
- package/src/generated/managers/testManager.ts +46 -0
- package/src/hooks/HookRegistry.ts +16 -0
- package/src/hooks/index.ts +15 -0
- package/src/i18n/I18n.ts +344 -0
- package/src/i18n/IncrementalDomainProvider.ts +153 -0
- package/src/i18n/InlineTranslationProvider.ts +4 -0
- package/src/i18n/LazyTranslationProvider.ts +102 -0
- package/src/i18n/MessagesRegistry.ts +4 -0
- package/src/i18n/Resolver.ts +4 -0
- package/src/i18n/__tests__/I18n.test.ts +169 -0
- package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
- package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
- package/src/i18n/__tests__/Resolver.test.ts +271 -0
- package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
- package/src/i18n/defaults/core.en.yml +55 -0
- package/src/i18n/defaults/core.fr.yml +55 -0
- package/src/i18n/index.ts +55 -0
- package/src/i18n/loaders/raw-modules.d.ts +15 -0
- package/src/i18n/loaders/yaml.ts +35 -0
- package/src/i18n/strategies.ts +4 -0
- package/src/i18n/types.ts +34 -0
- package/src/i18n/useI18n.ts +34 -0
- package/src/index.ts +453 -0
- package/src/kernel/EventRouter.ts +23 -0
- package/src/kernel/Kernel.api.ts +79 -0
- package/src/kernel/Kernel.auth.ts +120 -0
- package/src/kernel/Kernel.i18n.ts +29 -0
- package/src/kernel/Kernel.modules.ts +170 -0
- package/src/kernel/Kernel.registries.ts +237 -0
- package/src/kernel/Kernel.routing.ts +334 -0
- package/src/kernel/Kernel.ts +460 -0
- package/src/kernel/Kernel.types.ts +263 -0
- package/src/kernel/Kernel.vue.ts +304 -0
- package/src/kernel/KernelContext.entities.ts +80 -0
- package/src/kernel/KernelContext.events.ts +57 -0
- package/src/kernel/KernelContext.i18n.ts +37 -0
- package/src/kernel/KernelContext.permissions.ts +38 -0
- package/src/kernel/KernelContext.routing.ts +280 -0
- package/src/kernel/KernelContext.ts +252 -0
- package/src/kernel/KernelContext.types.ts +173 -0
- package/src/kernel/KernelContext.zones.ts +54 -0
- package/src/kernel/Module.ts +169 -0
- package/src/kernel/ModuleLoader.ts +645 -0
- package/src/kernel/SSEBridge.ts +11 -0
- package/src/kernel/SignalBus.ts +48 -0
- package/src/kernel/index.ts +56 -0
- package/src/module/index.ts +33 -0
- package/src/module/moduleRegistry.ts +492 -0
- package/src/modules/debug/AuthCollector.ts +501 -0
- package/src/modules/debug/Collector.ts +20 -0
- package/src/modules/debug/DebugBridge.ts +14 -0
- package/src/modules/debug/DebugModule.ts +220 -0
- package/src/modules/debug/EntitiesCollector.ts +839 -0
- package/src/modules/debug/ErrorCollector.ts +4 -0
- package/src/modules/debug/I18nCollector.ts +9 -0
- package/src/modules/debug/LocalStorageAdapter.ts +7 -0
- package/src/modules/debug/RouterCollector.ts +449 -0
- package/src/modules/debug/SignalCollector.ts +4 -0
- package/src/modules/debug/ToastCollector.ts +4 -0
- package/src/modules/debug/ZonesCollector.ts +447 -0
- package/src/modules/debug/components/DebugBar.vue +31 -0
- package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
- package/src/modules/debug/components/index.ts +9 -0
- package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
- package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
- package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
- package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
- package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
- package/src/modules/debug/components/panels/index.ts +16 -0
- package/src/modules/debug/index.ts +58 -0
- package/src/modules/debug/styles.scss +2671 -0
- package/src/notifications/NotificationBadge.vue +35 -0
- package/src/notifications/NotificationListener.vue +71 -0
- package/src/notifications/NotificationModule.ts +76 -0
- package/src/notifications/NotificationPanel.vue +478 -0
- package/src/notifications/NotificationStore.ts +288 -0
- package/src/notifications/index.ts +42 -0
- package/src/notifications/styles.scss +32 -0
- package/src/orchestrator/Orchestrator.ts +427 -0
- package/src/orchestrator/index.ts +8 -0
- package/src/orchestrator/useOrchestrator.ts +80 -0
- package/src/plugin.ts +251 -0
- package/src/query/FilterQuery.ts +306 -0
- package/src/query/QueryExecutor.ts +341 -0
- package/src/query/index.ts +16 -0
- package/src/security/EntityRolesProvider.ts +385 -0
- package/src/security/PermissionMatcher.ts +152 -0
- package/src/security/PermissionRegistry.ts +273 -0
- package/src/security/PersistableRolesProvider.ts +584 -0
- package/src/security/RolesManager.ts +136 -0
- package/src/security/RolesProvider.ts +167 -0
- package/src/security/RolesProviderStorage.ts +211 -0
- package/src/security/SecurityModule.ts +94 -0
- package/src/security/StaticRolesProvider.ts +117 -0
- package/src/security/UsersManager.ts +163 -0
- package/src/security/index.ts +77 -0
- package/src/security/pages/RoleForm.vue +276 -0
- package/src/security/pages/RoleList.vue +123 -0
- package/src/styles/_alerts.scss +95 -0
- package/src/styles/_buttons.scss +391 -0
- package/src/styles/_cards.scss +241 -0
- package/src/styles/_code.scss +78 -0
- package/src/styles/_dialogs.scss +55 -0
- package/src/styles/_filter-bar.scss +70 -0
- package/src/styles/_forms.scss +434 -0
- package/src/styles/_helpers.scss +153 -0
- package/src/styles/_lists.scss +225 -0
- package/src/styles/_main.scss +356 -0
- package/src/styles/_markdown.scss +88 -0
- package/src/styles/_responsive.scss +255 -0
- package/src/styles/_show-pages.scss +186 -0
- package/src/styles/_sidebar.scss +380 -0
- package/src/styles/_states.scss +115 -0
- package/src/styles/_stats.scss +47 -0
- package/src/styles/_variables.scss +224 -0
- package/src/styles/index.scss +38 -0
- package/src/styles/theme/_components.scss +331 -0
- package/src/styles/theme/_index.scss +10 -0
- package/src/styles/theme/_tokens.scss +147 -0
- package/src/styles/theme/_utilities.scss +175 -0
- package/src/toast/ToastBridgeModule.ts +68 -0
- package/src/toast/ToastListener.vue +55 -0
- package/src/toast/index.ts +26 -0
- package/src/toast/useSignalToast.ts +154 -0
- package/src/types/index.ts +285 -0
- package/src/types/pluralize.d.ts +19 -0
- package/src/types/quarkernel.d.ts +50 -0
- package/src/utils/formatters.ts +174 -0
- package/src/utils/index.ts +35 -0
- package/src/utils/transformers.ts +131 -0
- package/src/vite/qdadmDebugPlugin.ts +401 -0
- package/src/vite-env.d.ts +50 -0
- package/src/zones/ZoneRegistry.ts +934 -0
- package/src/zones/index.ts +29 -0
- 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
|
+
}
|